| Jeroen Demeyer on Tue, 19 Jan 2016 10:20:20 +0100 |
[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]
| Re: Move readline interface to libpari |
On 2016-01-18 18:01, Bill Allombert wrote:
+/* readline */ +typedef char* (*readline_GF)(const char*, int); /* generator function */ We can probably dispense with the typedef readline_GF.
This is a version without readline_GF.
>From e1211496b3fa0c06a7f238f34fa1876977563b37 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 | 344 ++---------------------------------------
src/gp/texmacs.c | 38 +----
src/headers/paripriv.h | 51 ++++++
src/language/readline.c | 400 ++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 469 insertions(+), 367 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..6d4d671 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,8 @@ BEGINEXTERN
ENDEXTERN
/**************************************************************************/
-static int pari_rl_back;
+static pari_rl_interface pari_rl;
static int did_init_matched = 0;
-static entree *current_ep = NULL;
static int
change_state(const char *msg, ulong flag, int count)
@@ -71,7 +69,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 +77,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 +212,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 +228,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)
@@ -680,12 +355,21 @@ get_line_from_readline(const char *prompt, const char *prompt_cont, filtre_t *F)
return 1;
}
+static char**
+gp_completion(char *text, int START, int END)
+{
+ return pari_completion(&pari_rl, text, START, END);
+}
+
void
init_readline(void)
{
static int init_done = 0;
if (init_done) return;
+
+ pari_use_readline(pari_rl);
+
if (! GP_DATA->use_readline) GP_DATA->readline_state = 0;
init_done = 1;
init_histfile();
@@ -700,7 +384,7 @@ init_readline(void)
rl_special_prefixes = "~";
/* custom completer */
- rl_attempted_completion_function = (rl_completion_func_t*) pari_completion;
+ rl_attempted_completion_function = (rl_completion_func_t*) gp_completion;
/* we always want the whole list of completions under emacs */
if (GP_DATA->flags & gpd_EMACS) rl_completion_query_items = 0x8fff;
diff --git a/src/gp/texmacs.c b/src/gp/texmacs.c
index b49eabd..0bc9185 100644
--- a/src/gp/texmacs.c
+++ b/src/gp/texmacs.c
@@ -30,8 +30,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
/* READLINE INTERFACE */
/* */
/*******************************************************************/
+static pari_rl_interface pari_rl;
static int did_complete = 0;
-char **pari_completion(char *text, int START, int END);
#ifdef READLINE
BEGINEXTERN
@@ -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,11 +67,11 @@ 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(&pari_rl, pos);
/* text = start of expression we complete */
rl_end = strlen(s)-1;
rl_point = pos;
- matches = pari_completion(text, text - rl_line_buffer, pos);
+ matches = pari_completion(&pari_rl, text, text - rl_line_buffer, pos);
printf("%cscheme:(tuple",DATA_BEGIN);
if (matches)
{
@@ -255,6 +224,7 @@ init_texmacs(void)
#ifdef READLINE
printf("%ccommand:(cas-supports-completions-set! \"pari\")%c\n",
DATA_BEGIN, DATA_END);
+ pari_use_readline(pari_rl);
#endif
cb_pari_fgets_interactive = tm_fgets;
cb_pari_get_line_interactive = tm_get_line;
diff --git a/src/headers/paripriv.h b/src/headers/paripriv.h
index 8a1b650..8f1532e 100644
--- a/src/headers/paripriv.h
+++ b/src/headers/paripriv.h
@@ -500,6 +500,47 @@ 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 struct {
+ /* pointers to readline variables/functions */
+ char **line_buffer;
+ int *point;
+ int *end;
+ char **(*completion_matches)(const char *, char *(*)(const char*, int));
+ char *(*filename_completion_function)(const char *, int);
+ char *(*username_completion_function)(const char *, int);
+ 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 do the following:
+
+#include <readline.h>
+#include <paripriv.h>
+pari_rl_interface pari_rl;
+pari_use_readline(pari_rl);
+
+This will initialize the pari_rl structure. A pointer to this structure
+must be given as first argument to all PARI readline functions. */
+
+/* IMPLEMENTATION NOTE: this really must be a macro (not a function),
+ * since we refer to readline symbols. */
+#define pari_use_readline(pari_rl) \
+ (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 +681,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 +772,12 @@ GEN polint_triv(GEN xa, GEN ya);
void pari_init_rand(void);
+/* readline.c */
+
+char** pari_completion(pari_rl_interface *pari_rl, char *text, int START, int END);
+char* pari_completion_word(pari_rl_interface *pari_rl, long end);
+char** pari_completion_matches(pari_rl_interface *pari_rl, 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..0d2c766
--- /dev/null
+++ b/src/language/readline.c
@@ -0,0 +1,400 @@
+/* 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"
+
+/**************************************************************************/
+static entree *current_ep = NULL;
+
+/* do we add () at the end of completed word? (is it a function?) */
+static int
+add_paren(pari_rl_interface *rl, 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(pari_rl_interface *rl, int code, char **matches)
+{
+ if (add_paren(rl, code))
+ {
+ match_concat(matches,"()");
+ 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(pari_rl_interface *rl, int code, const char *text, char *(*f)(const char*, int))
+{
+ char **matches = rl->completion_matches(text, f);
+ if (matches && !matches[1]) treat_single(rl, 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(pari_rl_interface *rl, 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(pari_rl_interface *rl, char *text, int START, int END)
+{
+ int i, first=0, start=START;
+ char *line = *rl->line_buffer;
+
+ *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 && line[start] != '~') start--;
+ while (start && is_keyword_char(line[start])) start--;
+ if (line[start] == '~')
+ {
+ char *(*f)(const char*, int);
+ f = rl->username_completion_function;
+ for(i=start+1;i<=END;i++)
+ if (line[i] == '/') { f = rl->filename_completion_function; break; }
+ return get_matches(rl, -1, text, f);
+ }
+
+ while (line[first] && isspace((int)line[first])) first++;
+ switch (line[first])
+ {
+ case '\\':
+ if (first == start) return add_space(rl, start);
+ return get_matches(rl, -1, text, rl->filename_completion_function);
+ case '?':
+ if (line[first+1] == '?')
+ return get_matches(rl, -1, text, ext_help_generator);
+ return get_matches(rl, -1, text, command_generator);
+ }
+
+ while (start && line[start] != '('
+ && line[start] != ',') start--;
+ if (line[start] == '(' && start)
+ {
+ int iend, j,k;
+ entree *ep;
+ char buf[200];
+
+ i = start;
+
+ while (i && isspace((int)line[i-1])) i--;
+ iend = i;
+ while (i && is_keyword_char(line[i-1])) i--;
+
+ if (strncmp(line + i,"default",7) == 0)
+ return get_matches(rl, -2, text, default_generator);
+ if ( strncmp(line + i,"read",4) == 0
+ || strncmp(line + i,"write",5) == 0)
+ return get_matches(rl, -1, text, rl->filename_completion_function);
+
+ j = start + 1;
+ while (j <= END && isspace((int)line[j])) j++;
+ k = END;
+ while (k > j && isspace((int)line[k])) k--;
+ /* If we are in empty parens, insert the default arguments */
+ if ((GP_DATA->readline_state & DO_ARGS_COMPLETE) && k == j
+ && (line[j] == ')' || !line[j])
+ && (iend - i < (long)sizeof(buf))
+ && ( strncpy(buf, line + 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(line[i]))
+ {
+ if (line[i] == '.')
+ return get_matches(rl, -1, text, member_generator);
+ break;
+ }
+ return get_matches(rl, END, text, command_generator);
+}
+
+char *
+pari_completion_word(pari_rl_interface *rl, long end)
+{
+ char *line = *rl->line_buffer;
+ 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(pari_rl_interface *rl, 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, 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(rl, text, w, pos);
+ return matches;
+}
--
2.0.5