Jeroen Demeyer on Mon, 18 Jan 2016 14:54:26 +0100


[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

Re: Move readline interface to libpari


Bill,

This patch addresses some of the points you made. Let me know what you think.

Cheers,
Jeroen.
>From 0ed46bc097f96ba25545aa7a9ee8f945e90448e3 Mon Sep 17 00:00:00 2001
From: Jeroen Demeyer <jdemeyer@cage.ugent.be>
Date: Sun, 10 Jan 2016 12:54:44 +0100
Subject: [PATCH] Library interface to readline completion

---
 src/gp/gp.h             |   3 -
 src/gp/gp_rl.c          | 335 +--------------------------------------
 src/gp/texmacs.c        |  33 +---
 src/headers/paripriv.h  |  50 ++++++
 src/language/readline.c | 408 ++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 465 insertions(+), 364 deletions(-)
 create mode 100644 src/language/readline.c

diff --git a/src/gp/gp.h b/src/gp/gp.h
index ea4cee5..d89fdc9 100644
--- a/src/gp/gp.h
+++ b/src/gp/gp.h
@@ -21,9 +21,6 @@ void init_emacs(void);
 void init_readline(void);
 void init_texmacs(void);
 
-/* readline completions */
-extern const char *keyword_list[];
-
 /* gp specific routines */
 void dbg_down(long k);
 void dbg_up(long k);
diff --git a/src/gp/gp_rl.c b/src/gp/gp_rl.c
index 9919cae..ac658df 100644
--- a/src/gp/gp_rl.c
+++ b/src/gp/gp_rl.c
@@ -24,7 +24,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
 #include "gp.h"
 
 typedef int (*RLCI)(int, int); /* rl_complete and rl_insert functions */
-typedef char* (*GF)(const char*, int); /* generator function */
 
 BEGINEXTERN
 /* otherwise C++ compilers will choke on rl_message() prototype */
@@ -35,9 +34,7 @@ BEGINEXTERN
 ENDEXTERN
 
 /**************************************************************************/
-static int pari_rl_back;
 static int did_init_matched = 0;
-static entree *current_ep = NULL;
 
 static int
 change_state(const char *msg, ulong flag, int count)
@@ -71,7 +68,7 @@ pari_rl_complete(int count, int key)
 {
   int ret;
 
-  pari_rl_back = 0;
+  pari_rl.back = 0;
   if (count <= 0)
     return change_state("complete args", DO_ARGS_COMPLETE, count);
 
@@ -79,8 +76,8 @@ pari_rl_complete(int count, int key)
   if (rl_last_func == pari_rl_complete)
     rl_last_func = (RLCI) rl_complete; /* Make repeated TABs different */
   ret = ((RLCI)rl_complete)(count,key);
-  if (pari_rl_back && (pari_rl_back <= rl_point))
-    rl_point -= pari_rl_back;
+  if (pari_rl.back && (pari_rl.back <= rl_point))
+    rl_point -= pari_rl.back;
   rl_end_undo_group(); return ret;
 }
 
@@ -214,218 +211,6 @@ pari_rl_backward_sexp(int count, int key)
   return pari_rl_forward_sexp(-count, key);
 }
 
-/* do we add () at the end of completed word? (is it a function?) */
-static int
-add_paren(int end)
-{
-  entree *ep;
-  const char *s;
-
-  if (end < 0 || rl_line_buffer[end] == '(')
-    return 0; /* not from command_generator or already there */
-  ep = do_alias(current_ep); /* current_ep set in command_generator */
-  if (EpVALENCE(ep) < EpNEW)
-  { /* is it a constant masked as a function (e.g Pi)? */
-    s = ep->help; if (!s) return 1;
-    while (is_keyword_char(*s)) s++;
-    return (*s != '=');
-  }
-  switch(EpVALENCE(ep))
-  {
-    case EpVAR: return typ((GEN)ep->value) == t_CLOSURE;
-    case EpINSTALL: return 1;
-  }
-  return 0;
-}
-
-static void
-match_concat(char **matches, const char *s)
-{
-  matches[0] = (char*)pari_realloc((void*)matches[0],
-                                strlen(matches[0])+strlen(s)+1);
-  strcat(matches[0],s);
-}
-
-#define add_comma(x) (x==-2) /* from default_generator */
-
-/* a single match, possibly modify matches[0] in place */
-static void
-treat_single(int code, char **matches)
-{
-  if (add_paren(code))
-  {
-    match_concat(matches,"()");
-    pari_rl_back = 1;
-    if (rl_point == rl_end)
-    rl_completion_append_character = '\0'; /* Do not append space. */
-  }
-  else if (add_comma(code))
-    match_concat(matches,",");
-}
-#undef add_comma
-
-
-static char **
-matches_for_emacs(const char *text, char **matches)
-{
-  if (!matches) printf("@");
-  else
-  {
-    int i;
-    printf("%s@", matches[0] + strlen(text));
-    if (matches[1]) print_fun_list(matches+1,0);
-
-   /* we don't want readline to do anything, but insert some junk
-    * which will be erased by emacs.
-    */
-    for (i=0; matches[i]; i++) pari_free(matches[i]);
-    pari_free(matches);
-  }
-  matches = (char **) pari_malloc(2*sizeof(char *));
-  matches[0] = (char*)pari_malloc(2); sprintf(matches[0],"_");
-  matches[1] = NULL; printf("@E_N_D"); pari_flush();
-  return matches;
-}
-
-/* Attempt to complete on the contents of TEXT. 'code' is used to
- * differentiate between callers when a single match is found.
- * Return the array of matches, NULL if there are none. */
-static char **
-get_matches(int code, const char *text, GF f)
-{
-  char **matches = rl_completion_matches(text, f);
-  if (matches && !matches[1]) treat_single(code, matches);
-  if (GP_DATA->flags & gpd_EMACS) matches = matches_for_emacs(text,matches);
-  return matches;
-}
-
-static char *
-add_prefix(const char *name, const char *text, long junk)
-{
-  char *s = strncpy((char*)pari_malloc(strlen(name)+1+junk),text,junk);
-  strcpy(s+junk,name); return s;
-}
-static void
-init_prefix(const char *text, int *len, int *junk, char **TEXT)
-{
-  long l = strlen(text), j = l-1;
-  while (j >= 0 && is_keyword_char(text[j])) j--;
-  j++;
-  *TEXT = (char*)text + j;
-  *junk = j;
-  *len  = l - j;
-}
-
-static int
-is_internal(entree *ep) { return *ep->name == '_'; }
-
-/* Generator function for command completion.  STATE lets us know whether
- * to start from scratch; without any state (i.e. STATE == 0), then we
- * start at the top of the list. */
-static char *
-hashtable_generator(const char *text, int state, entree **hash)
-{
-  static int hashpos, len, junk;
-  static entree* ep;
-  static char *TEXT;
-
- /* If this is a new word to complete, initialize now:
-  *  + indexes hashpos (GP hash list) and n (keywords specific to long help).
-  *  + file completion and keyword completion use different word boundaries,
-  *    have TEXT point to the keyword start.
-  *  + save the length of TEXT for efficiency.
-  */
-  if (!state)
-  {
-    hashpos = 0; ep = hash[hashpos];
-    init_prefix(text, &len, &junk, &TEXT);
-  }
-
-  /* Return the next name which partially matches from the command list. */
-  for(;;)
-    if (!ep)
-    {
-      if (++hashpos >= functions_tblsz) return NULL; /* no names matched */
-      ep = hash[hashpos];
-    }
-    else if (is_internal(ep) || strncmp(ep->name,TEXT,len))
-      ep = ep->next;
-    else
-      break;
-  current_ep = ep; ep = ep->next;
-  return add_prefix(current_ep->name,text,junk);
-}
-/* Generator function for member completion.  STATE lets us know whether
- * to start from scratch; without any state (i.e. STATE == 0), then we
- * start at the top of the list. */
-static char *
-member_generator(const char *text, int state)
-{
-  static int hashpos, len, junk;
-  static entree* ep;
-  static char *TEXT;
-  entree **hash=functions_hash;
-
- /* If this is a new word to complete, initialize now:
-  *  + indexes hashpos (GP hash list) and n (keywords specific to long help).
-  *  + file completion and keyword completion use different word boundaries,
-  *    have TEXT point to the keyword start.
-  *  + save the length of TEXT for efficiency.
-  */
-  if (!state)
-  {
-    hashpos = 0; ep = hash[hashpos];
-    init_prefix(text, &len, &junk, &TEXT);
-  }
-
-  /* Return the next name which partially matches from the command list. */
-  for(;;)
-    if (!ep)
-    {
-      if (++hashpos >= functions_tblsz) return NULL; /* no names matched */
-      ep = hash[hashpos];
-    }
-    else if (ep->name[0]=='_' && ep->name[1]=='.'
-             && !strncmp(ep->name+2,TEXT,len))
-        break;
-    else
-        ep = ep->next;
-  current_ep = ep; ep = ep->next;
-  return add_prefix(current_ep->name+2,text,junk);
-}
-static char *
-command_generator(const char *text, int state)
-{ return hashtable_generator(text,state, functions_hash); }
-static char *
-default_generator(const char *text,int state)
-{ return hashtable_generator(text,state, defaults_hash); }
-
-static char *
-ext_help_generator(const char *text, int state)
-{
-  static int len, junk, n, def, key;
-  static char *TEXT;
-  if (!state) {
-    n = 0;
-    def = key = 1;
-    init_prefix(text, &len, &junk, &TEXT);
-  }
-  if (def)
-  {
-    char *s = default_generator(TEXT, state);
-    if (s) return add_prefix(s, text, junk);
-    def = 0;
-  }
-  if (key)
-  {
-    for ( ; keyword_list[n]; n++)
-      if (!strncmp(keyword_list[n],TEXT,len))
-        return add_prefix(keyword_list[n++], text, junk);
-    key = 0; state = 0;
-  }
-  return command_generator(text, state);
-}
-
 static void
 rl_print_aide(char *s, int flag)
 {
@@ -442,117 +227,6 @@ rl_print_aide(char *s, int flag)
   rl_refresh_line(0,0);
 }
 
-/* add a space between \<char> and following text. Attempting completion now
- * would delete char. Hitting <TAB> again will complete properly */
-static char **
-add_space(int start)
-{
-  char **m;
-  int p = rl_point + 1;
-  rl_point = start + 2;
-  rl_insert(1, ' '); rl_point = p;
-  /*better: fake an empty completion, but don't append ' ' after it! */
-  rl_completion_append_character = '\0';
-  m = (char**)pari_malloc(2 * sizeof(char*));
-  m[0] = (char*)pari_malloc(1); *(m[0]) = 0;
-  m[1] = NULL; return m;
-}
-
-char **
-pari_completion(char *text, int START, int END)
-{
-  int i, first=0, start=START;
-
-  rl_completion_append_character = ' ';
-  current_ep = NULL;
-/* If the line does not begin by a backslash, then it is:
- * . an old command ( if preceded by "whatnow(" ).
- * . a default ( if preceded by "default(" ).
- * . a member function ( if preceded by "." + keyword_chars )
- * . a file name (in current directory) ( if preceded by 'read' or 'writexx' )
- * . a command */
-  if (start >=1 && rl_line_buffer[start] != '~') start--;
-  while (start && is_keyword_char(rl_line_buffer[start])) start--;
-  if (rl_line_buffer[start] == '~')
-  {
-    GF f = (GF)rl_username_completion_function;
-    for(i=start+1;i<=END;i++)
-      if (rl_line_buffer[i] == '/') { f = (GF)rl_filename_completion_function; break; }
-    return get_matches(-1, text, f);
-  }
-
-  while (rl_line_buffer[first] && isspace((int)rl_line_buffer[first])) first++;
-  switch (rl_line_buffer[first])
-  {
-    case '\\':
-      if (first == start) return add_space(start);
-      return get_matches(-1, text, rl_filename_completion_function);
-    case '?':
-      if (rl_line_buffer[first+1] == '?')
-        return get_matches(-1, text, ext_help_generator);
-      return get_matches(-1, text, command_generator);
-  }
-
-  while (start && rl_line_buffer[start] != '('
-               && rl_line_buffer[start] != ',') start--;
-  if (rl_line_buffer[start] == '(' && start)
-  {
-    int iend, j,k;
-    entree *ep;
-    char buf[200];
-
-    i = start;
-
-    while (i && isspace((int)rl_line_buffer[i-1])) i--;
-    iend = i;
-    while (i && is_keyword_char(rl_line_buffer[i-1])) i--;
-
-    if (strncmp(rl_line_buffer + i,"default",7) == 0)
-      return get_matches(-2, text, default_generator);
-    if ( strncmp(rl_line_buffer + i,"read",4)  == 0
-      || strncmp(rl_line_buffer + i,"write",5) == 0)
-      return get_matches(-1, text, rl_filename_completion_function);
-
-    j = start + 1;
-    while (j <= END && isspace((int)rl_line_buffer[j])) j++;
-    k = END;
-    while (k > j && isspace((int)rl_line_buffer[k])) k--;
-    /* If we are in empty parens, insert the default arguments */
-    if ((GP_DATA->readline_state & DO_ARGS_COMPLETE) && k == j
-         && (rl_line_buffer[j] == ')' || !rl_line_buffer[j])
-         && (iend - i < (long)sizeof(buf))
-         && ( strncpy(buf, rl_line_buffer + i, iend - i),
-              buf[iend - i] = 0, 1)
-         && (ep = is_entry(buf)) && ep->help)
-    {
-      const char *s = ep->help;
-      while (is_keyword_char(*s)) s++;
-      if (*s++ == '(')
-      { /* function call: insert arguments */
-        const char *e = s;
-        while (*e && *e != ')' && *e != '(') e++;
-        if (*e == ')')
-        { /* we just skipped over the arguments in short help text */
-          char *str = strncpy((char*)pari_malloc(e-s + 1), s, e-s);
-          char **ret = (char**)pari_malloc(sizeof(char*)*2);
-          str[e-s] = 0;
-          ret[0] = str; ret[1] = NULL;
-          if (GP_DATA->flags & gpd_EMACS) ret = matches_for_emacs("",ret);
-          return ret;
-        }
-      }
-    }
-  }
-  for(i = END-1; i >= start; i--)
-    if (!is_keyword_char(rl_line_buffer[i]))
-    {
-      if (rl_line_buffer[i] == '.')
-        return get_matches(-1, text, member_generator);
-      break;
-    }
-  return get_matches(END, text, command_generator);
-}
-
 /* long help if count < 0 */
 static int
 rl_short_help(int count, int key)
@@ -686,6 +360,9 @@ init_readline(void)
   static int init_done = 0;
 
   if (init_done) return;
+
+  pari_use_readline();
+
   if (! GP_DATA->use_readline) GP_DATA->readline_state = 0;
   init_done = 1;
   init_histfile();
diff --git a/src/gp/texmacs.c b/src/gp/texmacs.c
index b49eabd..460cd55 100644
--- a/src/gp/texmacs.c
+++ b/src/gp/texmacs.c
@@ -59,37 +59,6 @@ print_escape_string(char *s)
   *t = '\0'; puts(t0); pari_free(t0);
 }
 
-static char *
-completion_word(long end)
-{
-  char *s = rl_line_buffer + end, *found_quote = NULL;
-  long i;
-  /* truncate at cursor position */
-  *s = 0;
-  /* first look for unclosed string */
-  for (i=0; i < end; i++)
-  {
-    switch(rl_line_buffer[i])
-    {
-      case '"':
-        found_quote = found_quote? NULL: rl_line_buffer + i;
-        break;
-
-      case '\\': i++; break;
-    }
-
-  }
-  if (found_quote) return found_quote + 1; /* return next char after quote */
-
-  /* else find beginning of word */
-  while (s >  rl_line_buffer)
-  {
-    s--;
-    if (!is_keyword_char(*s)) { s++; break; }
-  }
-  return s;
-}
-
 /* completion required, cursor on s + pos. Complete wrt strict left prefix */
 static void
 tm_completion(const char *s, long pos)
@@ -98,7 +67,7 @@ tm_completion(const char *s, long pos)
 
   if (rl_line_buffer) pari_free(rl_line_buffer);
   rl_line_buffer = pari_strdup(s);
-  text = completion_word(pos);
+  text = pari_completion_word(rl_line_buffer, pos);
   /* text = start of expression we complete */
   rl_end = strlen(s)-1;
   rl_point = pos;
diff --git a/src/headers/paripriv.h b/src/headers/paripriv.h
index 8a1b650..0d02d29 100644
--- a/src/headers/paripriv.h
+++ b/src/headers/paripriv.h
@@ -500,6 +500,44 @@ int input_loop(filtre_t *F, input_method *IM);
 char *file_input(char **s0, int junk, input_method *IM, filtre_t *F);
 char *file_getline(Buffer *b, char **s0, input_method *IM);
 
+/* readline */
+typedef char* (*readline_GF)(const char*, int); /* generator function */
+
+typedef struct {
+  /* pointers to readline variables/functions */
+  char **line_buffer;
+  int *point;
+  int *end;
+  char **(*completion_matches)(const char *, readline_GF);
+  readline_GF filename_completion_function;
+  readline_GF username_completion_function;
+  int (*insert)(int, int);
+  int *completion_append_character;
+
+  /* PARI-specific */
+  int back;  /* rewind the cursor by this number of chars */
+} pari_rl_interface;
+
+/* Code which wants to use readline needs to call this macro
+ * pari_use_readline() after including <readline.h>. This will set the
+ * pari_rl structure such that the readline functions work.
+ *
+ * IMPLEMENTATION NOTE: this really must be a macro (not a function),
+ * since we refer to readline symbols.
+ */
+#define pari_use_readline() \
+    pari_rl.line_buffer = &rl_line_buffer, \
+    pari_rl.point = &rl_point, \
+    pari_rl.end = &rl_end, \
+    pari_rl.completion_matches = &rl_completion_matches, \
+    pari_rl.filename_completion_function = &rl_filename_completion_function, \
+    pari_rl.username_completion_function = &rl_username_completion_function, \
+    pari_rl.insert = &rl_insert, \
+    pari_rl.completion_append_character = &rl_completion_append_character, \
+    pari_rl.back = 0, \
+    pari_rl
+
+
 /* By files */
 
 /* Qfb.c */
@@ -640,6 +678,10 @@ GEN     gsubst_expr(GEN pol, GEN from, GEN to);
 GEN     poltoser(GEN x, long v, long prec);
 GEN     rfractoser(GEN x, long v, long prec);
 
+/* gplib.c */
+
+extern const char *keyword_list[];
+
 /* hyperell.c */
 
 GEN     ZlXQX_hyperellpadicfrobenius(GEN H, GEN T, ulong p, long n);
@@ -727,6 +769,14 @@ GEN     polint_triv(GEN xa, GEN ya);
 
 void    pari_init_rand(void);
 
+/* readline.c */
+
+extern pari_rl_interface pari_rl;
+
+char*   pari_completion_word(char *line, long end);
+char**  pari_completion(char *text, int START, int END);
+char**  pari_completion_matches(const char *s, long pos, long *wordpos);
+
 /* rootpol.c */
 
 GEN     FFT(GEN x, GEN Omega);
diff --git a/src/language/readline.c b/src/language/readline.c
new file mode 100644
index 0000000..de85780
--- /dev/null
+++ b/src/language/readline.c
@@ -0,0 +1,408 @@
+/* Copyright (C) 2000  The PARI group.
+
+This file is part of the PARI/GP package.
+
+PARI/GP 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. It is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY WHATSOEVER.
+
+Check the License for details. You should have received a copy of it, along
+with the package; see the file 'COPYING'. If not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
+
+/*******************************************************************/
+/*                                                                 */
+/*                 INTERFACE TO READLINE COMPLETION                */
+/*                                                                 */
+/*******************************************************************/
+#include "pari.h"
+#include "paripriv.h"
+
+pari_rl_interface pari_rl;
+
+#define rl_line_buffer (*pari_rl.line_buffer)
+#define rl_point (*pari_rl.point)
+#define rl_end (*pari_rl.end)
+#define rl_completion_matches (*pari_rl.completion_matches)
+#define rl_filename_completion_function (*pari_rl.filename_completion_function)
+#define rl_username_completion_function (*pari_rl.username_completion_function)
+#define rl_insert (*pari_rl.insert)
+#define rl_completion_append_character (*pari_rl.completion_append_character)
+
+/**************************************************************************/
+static entree *current_ep = NULL;
+
+/* do we add () at the end of completed word? (is it a function?) */
+static int
+add_paren(int end)
+{
+  entree *ep;
+  const char *s;
+
+  if (end < 0 || rl_line_buffer[end] == '(')
+    return 0; /* not from command_generator or already there */
+  ep = do_alias(current_ep); /* current_ep set in command_generator */
+  if (EpVALENCE(ep) < EpNEW)
+  { /* is it a constant masked as a function (e.g Pi)? */
+    s = ep->help; if (!s) return 1;
+    while (is_keyword_char(*s)) s++;
+    return (*s != '=');
+  }
+  switch(EpVALENCE(ep))
+  {
+    case EpVAR: return typ((GEN)ep->value) == t_CLOSURE;
+    case EpINSTALL: return 1;
+  }
+  return 0;
+}
+
+static void
+match_concat(char **matches, const char *s)
+{
+  matches[0] = (char*)pari_realloc((void*)matches[0],
+                                strlen(matches[0])+strlen(s)+1);
+  strcat(matches[0],s);
+}
+
+#define add_comma(x) (x==-2) /* from default_generator */
+
+/* a single match, possibly modify matches[0] in place */
+static void
+treat_single(int code, char **matches)
+{
+  if (add_paren(code))
+  {
+    match_concat(matches,"()");
+    pari_rl.back = 1;
+    if (rl_point == rl_end)
+    rl_completion_append_character = '\0'; /* Do not append space. */
+  }
+  else if (add_comma(code))
+    match_concat(matches,",");
+}
+#undef add_comma
+
+
+static char **
+matches_for_emacs(const char *text, char **matches)
+{
+  if (!matches) printf("@");
+  else
+  {
+    int i;
+    printf("%s@", matches[0] + strlen(text));
+    if (matches[1]) print_fun_list(matches+1,0);
+
+   /* we don't want readline to do anything, but insert some junk
+    * which will be erased by emacs.
+    */
+    for (i=0; matches[i]; i++) pari_free(matches[i]);
+    pari_free(matches);
+  }
+  matches = (char **) pari_malloc(2*sizeof(char *));
+  matches[0] = (char*)pari_malloc(2); sprintf(matches[0],"_");
+  matches[1] = NULL; printf("@E_N_D"); pari_flush();
+  return matches;
+}
+
+/* Attempt to complete on the contents of TEXT. 'code' is used to
+ * differentiate between callers when a single match is found.
+ * Return the array of matches, NULL if there are none. */
+static char **
+get_matches(int code, const char *text, readline_GF f)
+{
+  char **matches = rl_completion_matches(text, f);
+  if (matches && !matches[1]) treat_single(code, matches);
+  if (GP_DATA->flags & gpd_EMACS) matches = matches_for_emacs(text,matches);
+  return matches;
+}
+
+static char *
+add_prefix(const char *name, const char *text, long junk)
+{
+  char *s = strncpy((char*)pari_malloc(strlen(name)+1+junk),text,junk);
+  strcpy(s+junk,name); return s;
+}
+static void
+init_prefix(const char *text, int *len, int *junk, char **TEXT)
+{
+  long l = strlen(text), j = l-1;
+  while (j >= 0 && is_keyword_char(text[j])) j--;
+  j++;
+  *TEXT = (char*)text + j;
+  *junk = j;
+  *len  = l - j;
+}
+
+static int
+is_internal(entree *ep) { return *ep->name == '_'; }
+
+/* Generator function for command completion.  STATE lets us know whether
+ * to start from scratch; without any state (i.e. STATE == 0), then we
+ * start at the top of the list. */
+static char *
+hashtable_generator(const char *text, int state, entree **hash)
+{
+  static int hashpos, len, junk;
+  static entree* ep;
+  static char *TEXT;
+
+ /* If this is a new word to complete, initialize now:
+  *  + indexes hashpos (GP hash list) and n (keywords specific to long help).
+  *  + file completion and keyword completion use different word boundaries,
+  *    have TEXT point to the keyword start.
+  *  + save the length of TEXT for efficiency.
+  */
+  if (!state)
+  {
+    hashpos = 0; ep = hash[hashpos];
+    init_prefix(text, &len, &junk, &TEXT);
+  }
+
+  /* Return the next name which partially matches from the command list. */
+  for(;;)
+    if (!ep)
+    {
+      if (++hashpos >= functions_tblsz) return NULL; /* no names matched */
+      ep = hash[hashpos];
+    }
+    else if (is_internal(ep) || strncmp(ep->name,TEXT,len))
+      ep = ep->next;
+    else
+      break;
+  current_ep = ep; ep = ep->next;
+  return add_prefix(current_ep->name,text,junk);
+}
+/* Generator function for member completion.  STATE lets us know whether
+ * to start from scratch; without any state (i.e. STATE == 0), then we
+ * start at the top of the list. */
+static char *
+member_generator(const char *text, int state)
+{
+  static int hashpos, len, junk;
+  static entree* ep;
+  static char *TEXT;
+  entree **hash=functions_hash;
+
+ /* If this is a new word to complete, initialize now:
+  *  + indexes hashpos (GP hash list) and n (keywords specific to long help).
+  *  + file completion and keyword completion use different word boundaries,
+  *    have TEXT point to the keyword start.
+  *  + save the length of TEXT for efficiency.
+  */
+  if (!state)
+  {
+    hashpos = 0; ep = hash[hashpos];
+    init_prefix(text, &len, &junk, &TEXT);
+  }
+
+  /* Return the next name which partially matches from the command list. */
+  for(;;)
+    if (!ep)
+    {
+      if (++hashpos >= functions_tblsz) return NULL; /* no names matched */
+      ep = hash[hashpos];
+    }
+    else if (ep->name[0]=='_' && ep->name[1]=='.'
+             && !strncmp(ep->name+2,TEXT,len))
+        break;
+    else
+        ep = ep->next;
+  current_ep = ep; ep = ep->next;
+  return add_prefix(current_ep->name+2,text,junk);
+}
+static char *
+command_generator(const char *text, int state)
+{ return hashtable_generator(text,state, functions_hash); }
+static char *
+default_generator(const char *text,int state)
+{ return hashtable_generator(text,state, defaults_hash); }
+
+static char *
+ext_help_generator(const char *text, int state)
+{
+  static int len, junk, n, def, key;
+  static char *TEXT;
+  if (!state) {
+    n = 0;
+    def = key = 1;
+    init_prefix(text, &len, &junk, &TEXT);
+  }
+  if (def)
+  {
+    char *s = default_generator(TEXT, state);
+    if (s) return add_prefix(s, text, junk);
+    def = 0;
+  }
+  if (key)
+  {
+    for ( ; keyword_list[n]; n++)
+      if (!strncmp(keyword_list[n],TEXT,len))
+        return add_prefix(keyword_list[n++], text, junk);
+    key = 0; state = 0;
+  }
+  return command_generator(text, state);
+}
+
+/* add a space between \<char> and following text. Attempting completion now
+ * would delete char. Hitting <TAB> again will complete properly */
+static char **
+add_space(int start)
+{
+  char **m;
+  int p = rl_point + 1;
+  rl_point = start + 2;
+  rl_insert(1, ' '); rl_point = p;
+  /*better: fake an empty completion, but don't append ' ' after it! */
+  rl_completion_append_character = '\0';
+  m = (char**)pari_malloc(2 * sizeof(char*));
+  m[0] = (char*)pari_malloc(1); *(m[0]) = 0;
+  m[1] = NULL; return m;
+}
+
+char **
+pari_completion(char *text, int START, int END)
+{
+  int i, first=0, start=START;
+
+  rl_completion_append_character = ' ';
+  current_ep = NULL;
+/* If the line does not begin by a backslash, then it is:
+ * . an old command ( if preceded by "whatnow(" ).
+ * . a default ( if preceded by "default(" ).
+ * . a member function ( if preceded by "." + keyword_chars )
+ * . a file name (in current directory) ( if preceded by 'read' or 'writexx' )
+ * . a command */
+  if (start >=1 && rl_line_buffer[start] != '~') start--;
+  while (start && is_keyword_char(rl_line_buffer[start])) start--;
+  if (rl_line_buffer[start] == '~')
+  {
+    readline_GF f = (readline_GF)rl_username_completion_function;
+    for(i=start+1;i<=END;i++)
+      if (rl_line_buffer[i] == '/') { f = (readline_GF)rl_filename_completion_function; break; }
+    return get_matches(-1, text, f);
+  }
+
+  while (rl_line_buffer[first] && isspace((int)rl_line_buffer[first])) first++;
+  switch (rl_line_buffer[first])
+  {
+    case '\\':
+      if (first == start) return add_space(start);
+      return get_matches(-1, text, rl_filename_completion_function);
+    case '?':
+      if (rl_line_buffer[first+1] == '?')
+        return get_matches(-1, text, ext_help_generator);
+      return get_matches(-1, text, command_generator);
+  }
+
+  while (start && rl_line_buffer[start] != '('
+               && rl_line_buffer[start] != ',') start--;
+  if (rl_line_buffer[start] == '(' && start)
+  {
+    int iend, j,k;
+    entree *ep;
+    char buf[200];
+
+    i = start;
+
+    while (i && isspace((int)rl_line_buffer[i-1])) i--;
+    iend = i;
+    while (i && is_keyword_char(rl_line_buffer[i-1])) i--;
+
+    if (strncmp(rl_line_buffer + i,"default",7) == 0)
+      return get_matches(-2, text, default_generator);
+    if ( strncmp(rl_line_buffer + i,"read",4)  == 0
+      || strncmp(rl_line_buffer + i,"write",5) == 0)
+      return get_matches(-1, text, rl_filename_completion_function);
+
+    j = start + 1;
+    while (j <= END && isspace((int)rl_line_buffer[j])) j++;
+    k = END;
+    while (k > j && isspace((int)rl_line_buffer[k])) k--;
+    /* If we are in empty parens, insert the default arguments */
+    if ((GP_DATA->readline_state & DO_ARGS_COMPLETE) && k == j
+         && (rl_line_buffer[j] == ')' || !rl_line_buffer[j])
+         && (iend - i < (long)sizeof(buf))
+         && ( strncpy(buf, rl_line_buffer + i, iend - i),
+              buf[iend - i] = 0, 1)
+         && (ep = is_entry(buf)) && ep->help)
+    {
+      const char *s = ep->help;
+      while (is_keyword_char(*s)) s++;
+      if (*s++ == '(')
+      { /* function call: insert arguments */
+        const char *e = s;
+        while (*e && *e != ')' && *e != '(') e++;
+        if (*e == ')')
+        { /* we just skipped over the arguments in short help text */
+          char *str = strncpy((char*)pari_malloc(e-s + 1), s, e-s);
+          char **ret = (char**)pari_malloc(sizeof(char*)*2);
+          str[e-s] = 0;
+          ret[0] = str; ret[1] = NULL;
+          if (GP_DATA->flags & gpd_EMACS) ret = matches_for_emacs("",ret);
+          return ret;
+        }
+      }
+    }
+  }
+  for(i = END-1; i >= start; i--)
+    if (!is_keyword_char(rl_line_buffer[i]))
+    {
+      if (rl_line_buffer[i] == '.')
+        return get_matches(-1, text, member_generator);
+      break;
+    }
+  return get_matches(END, text, command_generator);
+}
+
+char *
+pari_completion_word(char *line, long end)
+{
+  char *s = line + end, *found_quote = NULL;
+  long i;
+  /* truncate at cursor position */
+  *s = 0;
+  /* first look for unclosed string */
+  for (i=0; i < end; i++)
+  {
+    switch(line[i])
+    {
+      case '"':
+        found_quote = found_quote? NULL: line + i;
+        break;
+
+      case '\\': i++; break;
+    }
+
+  }
+  if (found_quote) return found_quote + 1; /* return next char after quote */
+
+  /* else find beginning of word */
+  while (s > line)
+  {
+    s--;
+    if (!is_keyword_char(*s)) { s++; break; }
+  }
+  return s;
+}
+
+char **
+pari_completion_matches(const char *s, long pos, long *wordpos)
+{
+  char *text;
+  char **matches;
+  long w;
+
+  if (rl_line_buffer) pari_free(rl_line_buffer);
+  rl_line_buffer = pari_strdup(s);
+
+  text = pari_completion_word(rl_line_buffer, pos);
+  w = text - rl_line_buffer;
+  if (wordpos) *wordpos = w;
+  /* text = start of expression we complete */
+  rl_end = strlen(s)-1;
+  rl_point = pos;
+  matches = pari_completion(text, w, pos);
+  return matches;
+}
-- 
2.0.5