Jeroen Demeyer on Tue, 12 Jan 2016 10:15:49 +0100


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

Move readline interface to libpari


The attached patch moves part of the readline interface from the gp executable to libpari. It is needed for my PARI Jupyter notebook and (as far as I can see) it doesn't break anything.

Jeroen.
commit 8695cba03fb66fa24bdffaf1510a812ab994e192
Author: Jeroen Demeyer <jdemeyer@cage.ugent.be>
Date:   Sun Jan 10 12:54:44 2016 +0100

    Library interface to readline completion

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..59ec573 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)
@@ -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..6383b33 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 = 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 82ad6ec..314ac42 100644
--- a/src/headers/paripriv.h
+++ b/src/headers/paripriv.h
@@ -500,6 +500,33 @@ 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 {
+  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;
+} readline_interface;
+
+/* Use this macro in user code after including <readline.h> */
+#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
+
+
 /* By files */
 
 /* Qfb.c */
@@ -640,6 +667,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 +758,15 @@ GEN     polint_triv(GEN xa, GEN ya);
 
 void    pari_init_rand(void);
 
+/* readline.c */
+
+extern int pari_rl_back;
+extern readline_interface pari_rl;
+
+char*   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..df310c4
--- /dev/null
+++ b/src/language/readline.c
@@ -0,0 +1,409 @@
+/* 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"
+
+readline_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)
+
+/**************************************************************************/
+int pari_rl_back;
+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 *
+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 = 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;
+}