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