/*******************************************************************/
/*******************************************************************/
/*                                                                 */
/*                 INTERFACE TO READLINE COMPLETION                */
/*                                                                 */
/*******************************************************************/
/*******************************************************************/
/* $Id: gp_rl.c,v 2.0.0.2 1997/12/14 20:11:49 karim Exp karim $ */
#include "genpari.h"
#include "anal.h"
#include "gp.h"

#ifdef READLINE
BEGINEXTERN
#include <readline.h>
char **pari_completion(char *text, int start, int end);
char *filename_completion_function(char *text,int state);
char *username_completion_function(char *text,int state);

int rl_completion_query_items;
int rl_bind_key_in_map ();
ENDEXTERN

void print_fun_list(char **matches, int nbli);
void aide(char *s, int flag);

extern default_type gp_default_list[], gp_member_list[];
extern char *keyword_list[];
static int under_emacs, add_help_keywords;
static entree *current_ep = NULL;

typedef char** (*CF)(char*, char * (*)()); /* completion function */
typedef char* (*GF)(char*,int); /* generator function */

/* Attempt to complete on the contents of TEXT. START and END show the
 * region of TEXT that contains the word to complete.  We can use the
 * entire line in case we want to do some simple parsing.  Return the
 * array of matches, or NULL if there aren't any.
 */
static char **
get_matches(int end, char *text, char* f(char*,int))
{
  char **matches = ((CF) completion_matches)(text, (char *(*)(ANYARG))f);

  if (matches && ! matches[1] /* only one match          */
	      && ! current_ep /* this is not a variable  */
              && end >= 0     /* from command_completion */
              && rl_line_buffer[end] != '(' )
  {
    int i=strlen(matches[0]);
    matches[0] = (char*) gprealloc(matches[0], i+2, i);
    strcat(matches[0],"(");
  }
  current_ep = NULL;

  if (under_emacs)
  {
    int i;
    if (!matches) printf("@");
    else
    {
      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++) free(matches[i]);
      free(matches);
    }
    matches = (char **) gpmalloc(2*sizeof(char *));
    matches[0] = gpmalloc(2); sprintf(matches[0],"_");
    matches[1] = NULL;
    printf("@E_N_D"); pariflush();
  }
  return matches;
}

static char *
add_junk(char *name, char *text, long junk)
{
  char *s = strncpy((char*) gpmalloc(strlen(name)+1+junk),text,junk);
  strcpy(s+junk,name); return s;
}

/* 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 *
command_generator (char *text, int  state)
{
  static int hashpos, len, junk, n;
  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)
  {
    n = hashpos = 0; ep=functions_hash[hashpos];
    len=strlen(text); junk=len-1;
    while (junk >= 0 && is_keyword_char(text[junk])) junk--;  
    junk++; len -= junk; TEXT = text + junk;
  }

  /* First check the keywords list */
  if (add_help_keywords)
  {
    for ( ; keyword_list[n]; n++)
      if (!strncmp(keyword_list[n],TEXT,len))
      {
        text = add_junk(keyword_list[n],text,junk);
        n++; return 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 = functions_hash[hashpos];
    }
    else if (strncmp(ep->name,TEXT,len)) 
      ep = ep->next;
    else
      break;
  if (EpVALENCE(ep)==EpVAR) current_ep = ep;
  text = add_junk(ep->name,text,junk);
  ep=ep->next; return text;
}

static char *
member_generator (char *text, int  state)
{
  static int n, len, junk;
  char *name, *def = NULL;

  if (!state)
  {
    n = 0; len = strlen (text); junk=len-1;
    while (junk >= 0 && is_keyword_char(text[junk])) junk--;  
    junk++; len -= junk;
  }

  do
    def = gp_member_list[n++].name;
  while ( def && strncmp(def,text+junk,len) );

  if (def)
  {
    name = strncpy((char*) gpmalloc(strlen(def) + 1 + junk),text,junk);
    strcpy(name+junk,def); return(name);
  }
  return NULL; /* no names matched */
}

#define DFLT 0
#define ENTREE 1

static char *
generator(void *list, char *text, int *nn, int len, int typ)
{
  char *def = NULL, *name;
  int n = *nn;

  /* Return the next name which partially matches from list.*/
  switch(typ)
  {
    case DFLT : 
      do
	def = (((default_type *) list)[n++]).name;
      while ( def && strncmp(def,text,len) );
      break;

    case ENTREE :
      do
	def = (((entree *) list)[n++]).name;
      while ( def && strncmp(def,text,len) );
  }

  *nn = n;
  if (def)
  {
    name = strcpy((char*) gpmalloc(strlen(def)+1), def);
    return name;
  }
  return NULL; /* no names matched */
}

static char *
old_generator(char *text,int state)
{
  static int n,len;
  static char *res;

  if (!state) { res = "a"; n=0; len=strlen(text); }
  if (res)
  {
    res = generator((void *)oldfonctions,text,&n,len,ENTREE);
    if (res) return res;
    n=0;
  }
  return generator((void *)functions_oldgp,text,&n,len,ENTREE);
}

static char *
default_generator (char *text,int state)
{
  static int n,len;

  if (!state) { n=0; len=strlen(text); }
  return generator(gp_default_list,text,&n,len,DFLT);
}

char **
pari_completion (char *text, int start, int end)
{
  char *s, *beg;
  int i, first=0;

/* 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 "." within 4 letters )
 * . a file name (in current directory) ( if preceded by "read(" )
 * . 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] == '~')
  {
    for(i=start+1;i<=end;i++)
      if (rl_line_buffer[i] == '/')
	return get_matches(-1,text,filename_completion_function);
    return get_matches(-1,text,username_completion_function);
  }

  while (rl_line_buffer[first] && isspace(rl_line_buffer[first])) first++;
  switch (rl_line_buffer[first])
  {
    case '\\':
      if (first == start) text++;
      return get_matches(-1,text,filename_completion_function);
    case '?':
      if (rl_line_buffer[first+1] == '?') add_help_keywords = 1;
      return get_matches(-1,text,command_generator);
  }

  while (start && rl_line_buffer[start] != '('
               && rl_line_buffer[start] != ',') start--;
  if (rl_line_buffer[start] == '(')
  {
    beg = rl_line_buffer + start;
    if (start >= 7)
    {
      s = beg-7;
      if (!strncmp(s,"default",7))
	return get_matches(-1,text,default_generator);
      if (!strncmp(s,"whatnow",7))
	return get_matches(-1,text,old_generator);
    }

    if (start>=4)
    {
      s = beg-4;
      if (!strncmp(s,"read",4))
	return get_matches(-1,text,filename_completion_function);
    }
  }
  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;
    }
  add_help_keywords = 0;
  return get_matches(end,text,command_generator);
}

static int
rl_short_help(int count, int key)
{
  int p=rl_point, off=p;
  FILE *save = outfile;
  long flag = h_RL;

  while (off && is_keyword_char(rl_line_buffer[off-1])) off--;
  rl_point = 0; outfile = rl_outstream;
  if (count < 0) flag |= h_LONG; /* long help */
  aide(rl_line_buffer + off, flag);
  rl_point = p; outfile = save; 
  rl_refresh_line(); return 0;
}

static int
rl_long_help(int count, int key)
{
  return rl_short_help(-1,key);
}

void
init_readline(int i)
{
  /* Allow conditional parsing of the ~/.inputrc file. */
  rl_readline_name = "Pari-GP";

  /* added ? and , */
  rl_basic_word_break_characters=" \t\n\"\\'`@$><=;|&{(?,";

  rl_special_prefixes="~";

  /* custom completer */
#ifdef CPPFunction_defined
  rl_attempted_completion_function = (CPPFunction *) pari_completion;
#else
  rl_attempted_completion_function = (Function *) pari_completion;
#endif

  /* we always want the whole list of completions under emacs */
  under_emacs=i; if (under_emacs) rl_completion_query_items = 0x8fff;

#  define Bind ((void(*)(int,Function*,Keymap)) rl_bind_key_in_map)
#  define Defun ((void(*)(const char*,Function*,int)) rl_add_defun)

  Defun("short-help", (Function*) rl_short_help, -1);
  Defun("long-help", (Function*) rl_long_help, -1);

  Bind('h', (Function*) rl_short_help, emacs_meta_keymap);
  Bind('H', (Function*) rl_long_help,  emacs_meta_keymap);
  Bind('h', (Function*) rl_short_help, vi_movement_keymap);
  Bind('H', (Function*) rl_long_help,  vi_movement_keymap);

#  ifdef EMACS_DOS_KEYMAP
     Bind(';', (char*) rl_short_help, emacs_dos_keymap); /* F1 */
     Bind('T', (char*) rl_long_help,  emacs_dos_keymap); /* Shift-F1 */
#  endif
}
#endif
