/* Copyright (c) 1996 by Lele Gaifax.  All Rights Reserved
 *
 * This file is part of Nothing (yet).
 *
 * $RCSfile: pyrl.m,v $
 * $Revision: 1.1 $
 * $Date: 1997/03/23 08:02:13 $
 *
 * Created Tue Oct  1 12:04:57 1996.
 */

/* This module encapsulate the GNU readline library facilities: by
   leaving them into an external module, you gain some Kbytes and
   flexibility, since the interpreter may load it only when it is
   needed, ie only in interactive sessions.

   To enable the use of this module, you must explicitly add the
   option `--with-readline' to the `configure' script: it will
   automatically select the needed libraries (readline.a for sure,
   adding whatever terminal library your OS is using), putting
   their names into the Modules/Makefile' variable `PYRL_LIBS'.

   Then a line in the Setup file like
        pyrl pyrl.c $(PYRL_LIBS)
   will include it the build process. */

/* OpenSTEP version:
   This is the very same module, but it is implemented as a bundle
   instead as of a shared module. This is very easy, given the 
   PyNSBundledModule class included in the core framework: just
   implement a subclass of it, with its +initModule method "wrapping"
   the usual initxxx function. See the end of this file for an
   example. */

#include <Python/Python.h>
#include "PyNSBundledModule.h"

#ifdef WITH_READLINE

#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "myproto.h"
#include "mymalloc.h"
#include "intrcheck.h"

#include <readline/readline.h>
#include <readline/history.h>
#include <setjmp.h>
#include <signal.h>


/* AIX requires this to be the first thing in the file.  */
#ifdef __GNUC__
#ifndef alloca                  /* predefined by NeXT's header */
# define alloca __builtin_alloca
#endif
#else
# if HAVE_ALLOCA_H
#  include <alloca.h>
# else
#  ifdef _AIX
#pragma alloca
#  else
#   ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
#   endif
#  endif
# endif
#endif

static inline char *
strdup (const char *s)
{
  char *dup = malloc (strlen (s) + 1);


  return strcpy (dup, s);
}

static jmp_buf jbuf;

/* ARGSUSED */
static RETSIGTYPE
onintr(sig)
        int sig;
{
        longjmp(jbuf, sig);
}

/* Appends to LIST the ``papabile'' symbols in DICT, that may actually
   be a dictionary, a list, or an object responding to .keys().
   Returns the number of added symbols, or 0 on errors. */
static int
append_papabile_symbols (PyObject *list,
                         PyObject *dict,
                         const char *text,
                         int len)
{
  PyObject *keys;
  int count;

  if (PyDict_Check (dict))
    {
      keys = PyDict_Keys (dict);
      if (!keys)
        return 0;
      
      if (PyList_Sort (keys) != 0)
        {
          Py_DECREF (keys);
          return 0;
        }
    }
  else if (PyList_Check (dict))
    {
      keys = dict;
      Py_INCREF (keys);
    }
  else
    keys = PyObject_CallMethod (dict, "keys", NULL);

  count = 0;
  if (keys)
    {
      unsigned int idx = PyList_Size (keys);
      
      while (idx--)
        {
          PyObject *localsym = PyList_GetItem (keys, idx);
          
          if (strncmp (text, PyString_AsString (localsym), len) == 0)
            {
              PyList_Append (list, localsym);
              count++;
            }
        }

      Py_DECREF (keys);
    }
  else
    PyErr_Clear();
      
  return count;
}

/* If STATUS==0, collects the symbols from __main__.__dict__ and from
   __builtins__.__dict__, then build a list of those beginning with
   TEXT.
   Returns the next symbol's name if there is one, NULL otherwise.
   
   This is called from the readline's completer function. */
static char *
try_complete_local_symbol (char *text, int state)
{
  static PyObject *symbols_list = NULL;
  static int symbols_list_idx;
  
  if (!state)
    {
      PyObject *symbols;
      int len;
      
      Py_XDECREF (symbols_list);
      symbols_list = NULL;
      symbols_list_idx = 0;

      symbols_list = PyList_New(0);
      if (!symbols_list)
        return NULL;

      len = strlen (text);
      
      symbols = PyObject_GetAttrString (PyImport_AddModule ("__main__"), "__dict__");
      if (symbols)
        {
          symbols_list_idx += append_papabile_symbols (symbols_list,
                                                       symbols,
                                                       text,
                                                       len);
          Py_DECREF (symbols);
        }
      else
        PyErr_Clear();

      symbols = PyObject_GetAttrString (PyEval_GetBuiltins(), "__dict__");
      if (symbols)
        {
          symbols_list_idx += append_papabile_symbols (symbols_list,
                                                       symbols,
                                                       text,
                                                       len);
          Py_DECREF (symbols);
        }
      else
        PyErr_Clear();
    }

  if (symbols_list_idx > 0)
    {
      PyObject *localsym = PyList_GetItem (symbols_list, --symbols_list_idx);
      
      return strdup (PyString_AsString (localsym));
    }

  return NULL;
}

/* If STATUS==0, tries to understand from the entered TEXT the object
   we are writing the name, and then collects in a list the name of the
   symbols in both ``__dict__'' and ``__members__'' slots of it.

   Returns the next symbol's name if there is one, NULL otherwise.
   
   This is called from the readline's completer function. */
static char *
try_complete_member_symbol (char *text, int state)
{
  static PyObject *symbols_list = NULL;
  static int symbols_list_idx;
  static int prefix_len;
  
  if (!state)
    {
      int len;
      PyObject *symbols = PyImport_AddModule ("__main__");
      PyObject *list;
      const char *point, *start;
      
      Py_XDECREF (symbols_list);
      symbols_list = NULL;
      symbols_list_idx = 0;

      symbols_list = PyList_New(0);
      if (!symbols_list)
        return NULL;

      /* PyImport_AddModule() does not return a new reference! */
      Py_INCREF (symbols);
      
      start = text;
      while ((point = strchr (start, '.')))
        {
          int symlen = point-start;
          char *symname;

          if (!symlen)
            return NULL;

          symname = alloca (symlen+1);
          strncpy (symname, start, symlen);
          symname[symlen] = '\0';

          /* This is safe: if this is the first time, we INCREFed the
             module before the loop; if not, the previous GetAttrString()
             returned a new reference to a surely-existing-somewhere object. */
          Py_DECREF (symbols);
          
          symbols = PyObject_GetAttrString (symbols, symname);
          if (!symbols)
            {
              PyErr_Clear();
              return NULL;
            }

          start = point+1;
        }

      prefix_len = start-text;
      len = strlen (start);

      list = PyObject_GetAttrString (symbols, "__dict__");
      if (list)
        {
          symbols_list_idx += append_papabile_symbols (symbols_list,
                                                       list,
                                                       start,
                                                       len);
          Py_DECREF (list);
        }
      else
        PyErr_Clear();

      list = PyObject_GetAttrString (symbols, "__members__");
      if (list)
        {
          symbols_list_idx += append_papabile_symbols (symbols_list,
                                                       list,
                                                       start,
                                                       len);
          Py_DECREF (list);
        }
      else
        PyErr_Clear();

      list = PyObject_GetAttrString (symbols, "__methods__");
      if (list)
        {
          symbols_list_idx += append_papabile_symbols (symbols_list,
                                                       list,
                                                       start,
                                                       len);
          Py_DECREF (list);
        }
      else
        PyErr_Clear();
    }

  if (symbols_list_idx > 0)
    {
      PyObject *localsym = PyList_GetItem (symbols_list, --symbols_list_idx);
      extern char *strdup (const char *);
      const char *ls = PyString_AsString (localsym);
      char *complete;

      complete = alloca (prefix_len + PyString_Size (localsym) + 1);
      strncpy (complete, text, prefix_len);
      strcpy (complete+prefix_len, ls);
      return strdup (complete);
    }

  return NULL;
}

static inline char **
builtin_completion (char *text, int start, int end)
{
  if (end > start && strchr (text, '.'))
    return completion_matches (text, try_complete_member_symbol);
  else
    return completion_matches (text, try_complete_local_symbol);
}

static PyObject *python_completion_in_python;

/* If the current TEXT contains a point '.', assume we are accessing a
   member of an object and attempt to complete that; otherwise try to
   complete a local symbol. */
static char **
python_completion (char *text, int start, int end)
{
  /* Do not perform standard filename completion, even if we can't complete
     the symbol. */
  rl_attempted_completion_over = 1;

  if (python_completion_in_python)
    {
      PyTupleObject *args;
      PyObject *result;
      
      args = (PyTupleObject *) PyTuple_New (3);
      if (!args)
        return NULL;

      PyTuple_SET_ITEM (args, 0, PyString_FromString (text));
      PyTuple_SET_ITEM (args, 1, PyInt_FromLong (start));
      PyTuple_SET_ITEM (args, 2, PyInt_FromLong (end));

      result = PyEval_CallObject (python_completion_in_python, (PyObject *) args);

      Py_DECREF (args);

      if (result && PyList_Check (result))
        {
          int midx = PyList_Size (result);
          char **matches = malloc ((midx+1) * sizeof (char *));
          
          if (!matches)
            {
              PyErr_NoMemory();
              return NULL;
            }

          matches[midx] = NULL;
          while (midx--)
            matches[midx] = strdup (PyString_AsString (PyList_GetItem (result, midx)));

          Py_DECREF (result);

          return matches;
        }
      else
        {
          if (! result)
            {
              PyErr_Print();

              Py_DECREF (python_completion_in_python);
              python_completion_in_python = NULL;
            }
          else
            Py_DECREF (result);
          return NULL;
        }
    }
  else
    return builtin_completion (text, start, end);
}

static char *
py_readline (prompt)
        char *prompt;
{
  int n;
  char *p;
  RETSIGTYPE (*old_inthandler)();
  int sig;
  
#ifdef NeXT
  RETSIGTYPE (*old_conthandler)();

sigcont_received:
  old_conthandler = signal (SIGCONT, onintr);
#endif
  old_inthandler = signal (SIGINT, onintr);
  if ((sig = setjmp (jbuf)))
    {
#ifdef HAVE_SIGRELSE
      /* This seems necessary on SunOS 4.1 (Rasmus Hahn) */
      sigrelse(SIGINT);
#endif
      signal(SIGINT, old_inthandler);
#ifdef NeXT
      signal (SIGCONT, old_conthandler);
      if (sig == SIGCONT)
        goto sigcont_received;
#endif
      return NULL;
    }
  
  p = readline (prompt);
  signal (SIGINT, old_inthandler);
#ifdef NeXT
  signal (SIGCONT, old_conthandler);
#endif
  
  if (p == NULL)
    {
      p = malloc(1);
      if (p != NULL)
        *p = '\0';
      return p;
    }
  n = strlen (p);
  if (n > 0)
    add_history (p);
  if ((p = realloc (p, n+2)) != NULL)
    {
      p[n] = '\n';
      p[n+1] = '\0';
    }
  return p;
}

static char pyrl_set_completer_doc[] =
"Set a new completer function. The COMPLETER argument must be a callable\n\
object, and will be called with three arguments: the TEXT entered so far,\n\
START and END show the region of TEXT that contains the word to complete.\n\
Returns the previous completer object, or None if it was the builtin one.";

static PyObject *
pyrl_set_completer (PyObject *self, PyObject *args)
{
  PyObject *new;
  
  if (PyArg_ParseTuple (args, "O;completer", &new))
    {
      if (new == Py_None || PyCallable_Check (new))
        {
          PyObject *old = python_completion_in_python;

          if (new != Py_None)
            {
              python_completion_in_python = new;
              Py_INCREF (python_completion_in_python);
            }
          else
            python_completion_in_python = NULL;
          
          if (!old)
            {
              old = Py_None;
              Py_INCREF (old);
            }
          
          return old;
        }
      else
        PyErr_BadArgument();
    }

  return NULL;
}

static char pyrl_readline_doc[] =
"Read a line of input from the user with the GNU readline facility. If the\n\
optional PROMPT is not given, sys.ps1 is used instead.";

static PyObject *
pyrl_readline (PyObject *self, PyObject *args)
{
  char *prompt = NULL;

  if (PyArg_ParseTuple (args, "|s;prompt", &prompt))
    {
      char *line;
      
      if (prompt == NULL)
        {
          PyObject *ps1 = PySys_GetObject ("ps1");

          prompt = PyString_AsString (ps1);
        }

      line = py_readline (prompt);
      if (line)
        {
          PyObject *ret = PyString_FromString (line);

          free (line);
          return ret;
        }
      else
        {
          PyErr_NoMemory();
          return NULL;
        }
    }

  return NULL;
}

static char pyrl_builtin_completer_doc[] = "The builtin completer function";
static PyObject *
pyrl_builtin_completer (PyObject *self, PyObject *args)
{
  char *text;
  int start, end;

  if (PyArg_ParseTuple (args, "sii", &text, &start, &end))
    {
      char **result = builtin_completion (text, start, end);

      if (result)
        {
          PyObject *list;
          int ncompl;
          
          for (ncompl=0; result[ncompl]; ncompl++)
            /* do nothing */;

          list = PyList_New (ncompl);
          if (!list)
            return NULL;

          while (ncompl--)
            {
              PyObject *str = PyString_FromString (result[ncompl]);

              PyList_SetItem (list, ncompl, str);
              free (result[ncompl]);
            }
          free (result);
          
          return list;
        }
      else
        {
          Py_INCREF (Py_None);
          return Py_None;
        }
    }
  
  return NULL;
}    
          
extern char * (*PyOS_Readline)();

#endif /* WITH_READLINE */

static PyMethodDef pyrl_methods[] =
{
#ifdef WITH_READLINE
  { "set_completer",    (PyCFunction) pyrl_set_completer,       METH_VARARGS,   pyrl_set_completer_doc },
  { "builtin_completer",(PyCFunction) pyrl_builtin_completer,   METH_VARARGS,   pyrl_builtin_completer_doc },
  { "readline",         (PyCFunction) pyrl_readline,            METH_VARARGS,   pyrl_readline_doc },
#endif
  { 0, 0, 0, 0 }
};

static char pyrl_doc[] =
#ifdef WITH_READLINE
"A replacement for the builtin readline implementation that uses GNU readline.";
#else
"A placehold empty module";
#endif

@interface PyNSPyrlModule : PyNSBundledModule
{
}

+ (void) initModule;

@end

@implementation PyNSPyrlModule

+ (void) initModule
{
  Py_InitModule4 ("pyrl", pyrl_methods, pyrl_doc, NULL, PYTHON_API_VERSION);

#ifdef WITH_READLINE
  /* Force rebind of TAB to insert-tab */
  rl_bind_key ('\t', rl_insert);
  rl_bind_key_in_map ('\t', rl_complete, emacs_meta_keymap);
  rl_readline_name = "Python";
  rl_attempted_completion_function = (CPPFunction *) python_completion;
  rl_basic_word_break_characters = " \t\"'`<>=()[]{}";
  PyOS_Readline = py_readline;
#endif
}

@end
