// @(#)root/rint:$Name:  $:$Id: TTabCom.cxx,v 1.24 2004/07/06 16:34:31 rdm Exp $
// Author: Christian Lacunza <lacunza@cdfsg6.lbl.gov>   27/04/99

// Modified by Artur Szostak <artur@alice.phy.uct.ac.za> : 1 June 2003
//   Added support for namespaces.

/*************************************************************************
 * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

////////////////////////////////////////////////////////////////////////////
//                                                                        //
// TTabCom                                                                //
//                                                                        //
// This class performs basic tab completion.                              //
// You should be able to hit [TAB] to complete a partially typed:         //
//                                                                        //
//   username                                                             //
//   environment variable                                                 //
//   preprocessor directive                                               //
//   pragma                                                               //
//   filename (with a context-sensitive path)                             //
//   public member function or data member (including base classes)       //
//   global variable, function, or class name                             //
//                                                                        //
// Also, something like                                                   //
//                                                                        //
//   someObject->Func([TAB]                                               //
//   someObject.Func([TAB]                                                //
//   someClass::Func([TAB]                                                //
//   someClass var([TAB]                                                  //
//   new someClass([TAB]                                                  //
//                                                                        //
// will print a list of prototypes for the indicated                      //
// method or constructor.                                                 //
//                                                                        //
// Current limitations and bugs:                                          //
//                                                                        //
//  1. you can only use one member access operator at a time.             //
//     eg, this will work: gROOT->GetListOfG[TAB]                         //
//     but this will not:  gROOT->GetListOfGlobals()->Conta[TAB]          //
//                                                                        //
//  2. nothing is guaranteed to work on windows or VMS                    //
//     (for one thing, /bin/env and /etc/passwd are hardcoded)            //
//                                                                        //
//  3. CINT shortcut #2 is deliberately not supported.                    //
//     (using "operator.()" instead of "operator->()")                    //
//                                                                        //
//  4. most identifiers (including C++ identifiers, usernames,            //
//     environment variables, etc)                                        //
//     are restriceted to this character set: [_a-zA-Z0-9]                //
//     therefore, you won't be able to complete things like               //
//                                                                        //
//          operator new                                                  //
//          operator+                                                     //
//          etc                                                           //
//                                                                        //
//  5. ~whatever[TAB] always tries to complete a username.                //
//     use whitespace (~ whatever[TAB]) if you want to complete a global  //
//     identifier.                                                        //
//                                                                        //
//  6. CINT shortcut #3 is not supported when trying to complete          //
//     the name of a global object.  (it is supported when trying to      //
//     complete a member of a global object)                              //
//                                                                        //
//  7. the list of #pragma's is hardcoded                                 //
//     (ie not obtained from the interpreter at runtime)                  //
//     ==> user-defined #pragma's will not be recognized                  //
//                                                                        //
//  8. the system include directories are also hardcoded                  //
//     because i don't know how to get them from the interpreter.         //
//     fons, maybe they should be #ifdef'd for the different sytems?      //
//                                                                        //
//  9. the TabCom.FileIgnore resource is always applied, even if you      //
//     are not trying to complete a filename.                             //
//                                                                        //
// 10. anything in quotes is assumed to be a filename                     //
//     so (among other things) you can't complete a quoted class name:    //
//     eg, TClass class1( "TDict[TAB]                                     //
//     this won't work... looks for a file in pwd starting with TDict     //
//                                                                        //
// 11. the prototypes tend to omit the word "const" a lot.                //
//     this is a problem with ROOT or CINT.                               //
//                                                                        //
// 12. when listing ambiguous matches, only one column is used,           //
//     even if there are many completions.                                //
//                                                                        //
// 13. anonymous objects are not currently identified                     //
//     so, for example,                                                   //
//                                                                        //
//          root> printf( TString([TAB                                    //
//                                                                        //
//     gives an error message instead of listing TString's constructors.  //
//     (this could be fixed)                                              //
//                                                                        //
// 14. the routine that adds the "appendage" isn't smart enough to know   //
//     if it's already there:                                             //
//                                                                        //
//          root> TCanvas::Update()                                       //
//              press [TAB] here ^                                        //
//          root> TCanvas::Update()()                                     //
//     (this could be fixed)                                              //
//                                                                        //
// 15. the appendage is only applied if there is exactly 1 match.         //
//     eg, this                                                           //
//                                                                        //
//          root> G__at[TAB]                                              //
//          root> G__ateval                                               //
//                                                                        //
//     happens instead of this                                            //
//                                                                        //
//          root> G__at[TAB]                                              //
//          root> G__ateval(                                              //
//                                                                        //
//     because there are several overloaded versions of G__ateval().      //
//     (this could be fixed)                                              //
//                                                                        //
////////////////////////////////////////////////////////////////////////////

#include <stdio.h>

#ifdef HAVE_CONFIG
#include "config.h"
#endif

#include "TTabCom.h"
#include "TClass.h"
#include "TSystem.h"
#include "TROOT.h"
#include "TMethod.h"
#include "TEnv.h"
#include "TBenchmark.h"
#include "TError.h"
#include "TGlobal.h"
#include "TList.h"
#include "Getline.h"
#include "TFunction.h"
#include "TMethodArg.h"
#include "Riostream.h"
#include "Rstrstream.h"

//Direct CINT include
#include "DataMbr.h"
#include "common.h"


#define BUF_SIZE    1024        // must match value in C_Getline.c (for bounds checking)
#define IfDebug(x)  if(gDebug==TTabCom::kDebug) x

#ifdef R__WIN32
const char kDelim = ';';
#else
const char kDelim = ':';
#endif


ClassImp(TTabCom)
// ----------------------------------------------------------------------------
//
//             global/file scope variables
//
TTabCom *gTabCom = 0;


extern "C" int gl_root_tab_hook(char *buf, int /*prompt_width */ ,
                                int *pLoc)
{
   return gTabCom ? gTabCom->Hook(buf, pLoc) : -1;
}


// ----------------------------------------------------------------------------
//
//              constructors
//

TTabCom::TTabCom()
{
   fpDirectives = 0;
   fpPragmas = 0;
   fpGlobals = 0;
   fpGlobalFuncs = 0;
   fpClasses = 0;
   fpNamespaces = 0;
   fpUsers = 0;
   fpEnvVars = 0;
   fpFiles = 0;
   fpSysIncFiles = 0;

   InitPatterns();

   Gl_tab_hook = gl_root_tab_hook;
}

//
//              constructors
//
// ----------------------------------------------------------------------------


// ----------------------------------------------------------------------------
//
//              public member functions
//


void TTabCom::ClearClasses()
{
   if (!fpClasses)
      return;
   fpClasses->Delete(0);
   delete fpClasses;
   fpClasses = 0;

   // Since the namespace array is filled at the same time as fpClasses we
   // delete it at the same time.
   if (!fpNamespaces)
      return;
   fpNamespaces->Delete(0);
   delete fpNamespaces;
   fpNamespaces = 0;
}

void TTabCom::ClearCppDirectives()
{
   if (!fpDirectives)
      return;
   fpDirectives->Delete(0);
   delete fpDirectives;
   fpDirectives = 0;
}

void TTabCom::ClearEnvVars()
{
   if (!fpEnvVars)
      return;
   fpEnvVars->Delete(0);
   delete fpEnvVars;
   fpEnvVars = 0;
}

void TTabCom::ClearFiles()
{
   if (!fpFiles)
      return;
   fpFiles->Delete(0);
   delete fpFiles;
   fpFiles = 0;
}

void TTabCom::ClearGlobalFunctions()
{
   if (!fpGlobalFuncs)
      return;
   fpGlobalFuncs->Delete(0);
   delete fpGlobalFuncs;
   fpGlobalFuncs = 0;
}

void TTabCom::ClearGlobals()
{
   if (!fpGlobals)
      return;
   fpGlobals->Delete(0);
   delete fpGlobals;
   fpGlobals = 0;
}

void TTabCom::ClearPragmas()
{
   if (!fpPragmas)
      return;
   fpPragmas->Delete(0);
   delete fpPragmas;
   fpPragmas = 0;
}

void TTabCom::ClearSysIncFiles()
{
   if (!fpSysIncFiles)
      return;
   fpSysIncFiles->Delete(0);
   delete fpSysIncFiles;
   fpSysIncFiles = 0;
}

void TTabCom::ClearUsers()
{
   if (!fpUsers)
      return;
   fpUsers->Delete(0);
   delete fpUsers;
   fpUsers = 0;
}

void TTabCom::ClearAll()
{
   // clears all lists
   // except for user names and system include files.

   ClearClasses();
   ClearCppDirectives();
   ClearEnvVars();
   ClearFiles();
   ClearGlobalFunctions();
   ClearGlobals();
   ClearPragmas();
//   ClearSysIncFiles(); <-- this one stays cached
//   ClearUsers();       <-- this one stays cached
}

void TTabCom::RehashClasses()
{
   ClearClasses();
   GetListOfClasses();
}

void TTabCom::RehashCppDirectives()
{
   ClearCppDirectives();
   GetListOfCppDirectives();
}

void TTabCom::RehashEnvVars()
{
   ClearEnvVars();
   GetListOfEnvVars();
}

void TTabCom::RehashFiles()
{
   ClearFiles();                /* path unknown */
}                               // think about this

void TTabCom::RehashGlobalFunctions()
{
   ClearGlobalFunctions();
   GetListOfGlobalFunctions();
}

void TTabCom::RehashGlobals()
{
   ClearGlobals();
   GetListOfGlobals();
}

void TTabCom::RehashPragmas()
{
   ClearPragmas();
   GetListOfPragmas();
}

void TTabCom::RehashSysIncFiles()
{
   ClearSysIncFiles();
   GetListOfSysIncFiles();
}

void TTabCom::RehashUsers()
{
   ClearUsers();
   GetListOfUsers();
}

void TTabCom::RehashAll()
{
   // clears and then rebuilds all lists
   // except for user names and system include files.

   RehashClasses();
   RehashCppDirectives();
   RehashEnvVars();
   RehashFiles();
   RehashGlobalFunctions();
   RehashGlobals();
   RehashPragmas();
//   RehashSysIncFiles(); <-- this one stays cached
//   RehashUsers();       <-- this one stays cached
}

const TSeqCol *TTabCom::GetListOfClasses(void)
{
   if (!fpClasses) {
      // generate a text list of classes on disk
      const char *tmpfilename = tmpnam(0);
      FILE *fout = fopen(tmpfilename, "w");
      if (!fout) return 0;
      G__display_class(fout,"",0,0);
      fclose(fout);

      // open the file
      ifstream file1(tmpfilename);
      if (!file1) {
         Error("TTabCom::GetListOfClasses", "could not open file \"%s\"",
               tmpfilename);
         gSystem->Unlink(tmpfilename);
         return 0;
      }
      // skip the first 2 lines (which are just header info)
      file1.ignore(32000, '\n');
      file1.ignore(32000, '\n');

      // parse file, add to list
      fpClasses = new TContainer;
      fpNamespaces = new TContainer;
      TString line;
      while (file1) {
         line = "";
         line.ReadLine(file1, kFALSE);	// kFALSE ==> don't skip whitespace
         line = line(23, 32000);
// old way...
//             if (line.Index("class") >= 0)
//                  line = line(6, 32000);
//             else if (line.Index("enum") >= 0)
//                  line = line(5, 32000);
//             else if (line.Index("(unknown)") >= 0)
//                  line = line(10, 32000);
//             line = line("[^ ]*");
// new way...
         int index;
         Bool_t isanamespace = kFALSE;	// Flag used to check if we found a namespace name.
         if (0);
         else if ((index = line.Index(" class ")) >= 0)
            line = line(1 + index + 6, 32000);
         else if ((index = line.Index(" namespace ")) >= 0) {
            line = line(1 + index + 10, 32000);
            isanamespace = kTRUE;
         } else if ((index = line.Index(" struct ")) >= 0)
            line = line(1 + index + 7, 32000);
         else if ((index = line.Index(" enum ")) >= 0)
            line = line(1 + index + 5, 32000);
         else if ((index = line.Index(" (unknown) ")) >= 0)
            line = line(1 + index + 10, 32000);
         // 2 changes: 1. use spaces ^         ^          2. use offset ^^^^^ in case of long
         //               to reduce probablility that        filename which overflows
         //               these keywords will occur in       its field.
         //               filename or classname.
         line = line("[^ ]*");

         // If we find namespace names then add them to the fpNamespaces array and
         // not the classes array.
         if (isanamespace)
            fpNamespaces->Add(new TObjString(line.Data()));
         else
            fpClasses->Add(new TObjString(line.Data()));
      }

      // done with this file
      file1.close();
      gSystem->Unlink(tmpfilename);
   }

   return fpClasses;
}
const TSeqCol *TTabCom::GetListOfCppDirectives()
{
   if (!fpDirectives) {
      fpDirectives = new TContainer;

      fpDirectives->Add(new TObjString("if"));
      fpDirectives->Add(new TObjString("ifdef"));
      fpDirectives->Add(new TObjString("ifndef"));
      fpDirectives->Add(new TObjString("elif"));
      fpDirectives->Add(new TObjString("else"));
      fpDirectives->Add(new TObjString("endif"));
      fpDirectives->Add(new TObjString("include"));
      fpDirectives->Add(new TObjString("define"));
      fpDirectives->Add(new TObjString("undef"));
      fpDirectives->Add(new TObjString("line"));
      fpDirectives->Add(new TObjString("error"));
      fpDirectives->Add(new TObjString("pragma"));
   }

   return fpDirectives;
}
const TSeqCol *TTabCom::GetListOfFilesInPath(const char path[])
{
   // "path" should be initialized with a colon separated list of
   // system directories

   static TString previousPath;

   if (path && fpFiles && strcmp(path, previousPath) == 0) {
      return fpFiles;
   } else {
      ClearFiles();

      fpFiles = NewListOfFilesInPath(path);
   }

   return fpFiles;
}
const TSeqCol *TTabCom::GetListOfEnvVars()
{
   // calls "/bin/env"

   if (!fpEnvVars) {
      const char *tmpfilename = tmpnam(0);
      TString cmd;

#ifndef WIN32
      cmd = "/bin/env > ";
#else
      cmd = "set > ";
#endif
      cmd += tmpfilename;
      cmd += "\n";
      gSystem->Exec(cmd.Data());

      // open the file
      ifstream file1(tmpfilename);
      if (!file1) {
         Error("TTabCom::GetListOfEnvVars", "could not open file \"%s\"",
               tmpfilename);
         gSystem->Unlink(tmpfilename);
         return 0;
      }
      // parse, add
      fpEnvVars = new TContainer;
      TString line;
      while (file1)             // i think this loop goes one time extra which
         // results in an empty string in the list, but i don't think it causes any
         // problems.
      {
         line.ReadToDelim(file1, '=');
         file1.ignore(32000, '\n');
         fpEnvVars->Add(new TObjString(line.Data()));
      }

      file1.close();
      gSystem->Unlink(tmpfilename);
   }

   return fpEnvVars;
}

//______________________________________________________________________________
const TSeqCol *TTabCom::GetListOfGlobals()
{
   if (!fpGlobals) {

      fpGlobals = new TContainer;

      G__DataMemberInfo *a;
      int last = 0;
      int nglob = 0;

      // find the number of global objects
      G__DataMemberInfo t;
      while (t.Next())
         nglob++;

      for (int i = 0; i < nglob; i++) {
         a = new G__DataMemberInfo();
         a->Next();             // initial positioning

         for (int j = 0; j < last; j++)
            a->Next();

         // if name cannot be obtained no use to put in list
         if (a->IsValid() && a->Name()) {
            fpGlobals->Add(new TGlobal(a));
         } else
            delete a;

         last++;
      }
   }

   return fpGlobals;
}

//______________________________________________________________________________
const TSeqCol *TTabCom::GetListOfGlobalFunctions()
{
   if (!fpGlobalFuncs) {

      fpGlobalFuncs = new TContainer;

      G__MethodInfo *a;
      int last = 0;
      int nglob = 0;

      // find the number of global functions
      G__MethodInfo t;
      while (t.Next())
         nglob++;

      for (int i = 0; i < nglob; i++) {
         a = new G__MethodInfo();
         a->Next();             // initial positioning

         for (int j = 0; j < last; j++)
            a->Next();

         // if name cannot be obtained no use to put in list
         if (a->IsValid() && a->Name()) {
            fpGlobalFuncs->Add(new TFunction(a));
         } else
            delete a;

         last++;
      }
   }

   return fpGlobalFuncs;
}
const TSeqCol *TTabCom::GetListOfPragmas()
{
   if (!fpPragmas) {
      fpPragmas = new TContainer;

      fpPragmas->Add(new TObjString("ANSI "));
      fpPragmas->Add(new TObjString("autocompile "));
      fpPragmas->Add(new TObjString("bytecode "));
      fpPragmas->Add(new TObjString("compile "));
      fpPragmas->Add(new TObjString("endbytecode "));
      fpPragmas->Add(new TObjString("endcompile "));
      fpPragmas->Add(new TObjString("include "));
      fpPragmas->Add(new TObjString("includepath "));
      fpPragmas->Add(new TObjString("K&R "));
      fpPragmas->Add(new TObjString("link "));
      fpPragmas->Add(new TObjString("preprocess "));
      fpPragmas->Add(new TObjString("preprocessor "));
      fpPragmas->Add(new TObjString("security level"));
      // "setertti "  omitted. Ordinaly user should not use this statement
      // "setstdio "  omitted. Ordinaly user should not use this statement
      // "setstream " omitted. Ordinaly user should not use this statement
      // "stub"       omitted. Ordinaly user should not use this statement

   }

   return fpPragmas;
}
const TSeqCol *TTabCom::GetListOfSysIncFiles()
{
   if (!fpSysIncFiles) {
      fpSysIncFiles = NewListOfFilesInPath(GetSysIncludePath());
   }

   return fpSysIncFiles;

}
const TSeqCol *TTabCom::GetListOfUsers()
{
   // reads from "/etc/passwd"

   if (!fpUsers) {
      fpUsers = new TContainer;

      ifstream passwd;
      TString user;

      passwd.open("/etc/passwd");
      while (passwd) {
         user.ReadToDelim(passwd, ':');
         fpUsers->Add(new TObjString(user));
         passwd.ignore(32000, '\n');
      }
      passwd.close();
   }

   return fpUsers;
}

//
//              public member functions
//
// ----------------------------------------------------------------------------

// ----------------------------------------------------------------------------
//
//                           static utility funcitons
//

Char_t TTabCom::AllAgreeOnChar(int i, const TSeqCol * pList,
                               Int_t & nGoodStrings)
{
   //[static utility function]///////////////////////////////////////////
   //
   //  if all the strings in "*pList" have the same ith character,
   //  that character is returned.
   //  otherwise 0 is returned.
   //
   //  any string "s" for which "ExcludedByFignore(s)" is true
   //  will be ignored unless All the strings in "*pList"
   //  are "ExcludedByFignore()"
   //
   //  in addition, the number of strings which were not
   //  "ExcludedByFignore()" is returned in "nGoodStrings".
   //
   /////////////////////////////////////////////////////////////////////////

   assert(pList != 0);

   TIter next(pList);
   TObject *pObj;
   const char *s;
   char ch0;
   Bool_t isGood;
   Bool_t atLeast1GoodString;

   // init
   nGoodStrings = 0;
   atLeast1GoodString = kFALSE;

   // first look for a good string
   do {
      if ((pObj = next())) {
         s = pObj->GetName();
         isGood = !ExcludedByFignore(s);
         if (isGood) {
            atLeast1GoodString = kTRUE;
            nGoodStrings += 1;
         }
      } else {
         // reached end of list without finding a single good string.
         // just use the first one.
         next.Reset();
         pObj = next();
         s = pObj->GetName();
         break;
      }
   }
   while (!isGood);

   // found a good string...
   ch0 = s[i];

   // all subsequent good strings must have the same ith char
   do {
      if ((pObj = next())) {
         s = pObj->GetName();
         isGood = !ExcludedByFignore(s);
         if (isGood)
            nGoodStrings += 1;
      } else
         return ch0;
   }
   while (((int) strlen(s) >= i && s[i] == ch0) ||
          (atLeast1GoodString && !isGood));

   return 0;
}
void TTabCom::AppendListOfFilesInDirectory(const char dirName[],
                                           TSeqCol * pList)
{
   //[static utility function]/////////////////////////////
   //
   //  adds a TObjString to "*pList"
   //  for each entry found in the system directory "dirName"
   //
   //  directories that do not exist are silently ignored.
   //
   //////////////////////////////////////////////////////////

   assert(dirName != 0);
   assert(pList != 0);

   // open the directory
   void *dir = gSystem->OpenDirectory(dirName);

   // it is normal for this function to receive names of directories that do not exist.
   // they should be ignored and should not generate any error messages.
   if (!dir)
      return;

   // put each filename in the list
   const char *tmp_ptr;         // gSystem->GetDirEntry() returns NULL when no more files.
   TString fileName;

   while ((tmp_ptr = gSystem->GetDirEntry(dir))) {
      fileName = tmp_ptr;

      // skip "." and ".."
      if (fileName == "." || fileName == "..")
         continue;

      // add to list
      pList->Add(new TObjString(dirName + fileName.Prepend("/")));
   }
   // NOTE:
   // with a path like "/usr/include:/usr/include/CC:$ROOTDIR/include:$ROOTDIR/cint/include:..."
   // the above loop could get traversed 700 times or more.
   // ==> keep it minimal or it could cost whole seconds on slower machines.
   // also: TClonesArray doesn't help.

   // close the directory
   gSystem->FreeDirectory(dir);
}

// -----\/-------- homemade RTTI ---------------\/------------------------
TString TTabCom::DetermineClass(const char varName[])
{
   //[static utility function]/////////////////////////////
   //
   //  returns empty string on failure.
   //  otherwise returns something like this: "TROOT*".
   //  fails for non-class types (ie, int, char, etc).
   //  fails for pointers to functions.
   //
   ///////////////////////////////////


   ///////////////////////////////////
   //
   //  note that because of the strange way this function works,
   //  CINT will print
   //
   //     Error: No symbol asdf in current scope  FILE:/var/tmp/gaaa001HR LINE:1
   //
   //  if "varName" is not defined. (in this case, varName=="asdf")
   //  i don't know how to suppress this.
   //
   ///////////////////////////////////

   assert(varName != 0);
   IfDebug(cerr << "DetermineClass(\"" << varName << "\");" << endl);

   const char *tmpfile = tmpnam(0);
   TString cmd("gROOT->ProcessLine(\"");
   cmd += varName;
   cmd += "\"); > ";
   cmd += tmpfile;
   cmd += "\n";

   gROOT->ProcessLineSync(cmd.Data());
   // the type of the variable whose name is "varName"
   // should now be stored on disk in the file "tmpfile"

   TString type = "";
   int c;

   // open the file
   ifstream file1(tmpfile);
   if (!file1) {
      Error("TTabCom::DetermineClass", "could not open file \"%s\"",
            tmpfile);
      goto cleanup;
   }
   // first char should be '(', which we can ignore.
   c = file1.get();
   if (!file1 || c <= 0 || c == '*' || c != '(') {
      Error("TTabCom::DetermineClass", "variable \"%s\" not defined?",
            varName);
      goto cleanup;
   }
   IfDebug(cerr << (char) c << flush);

   // in case of success, "class TClassName*)0x12345" remains,
   // since the opening '(' was removed.
   file1 >> type;               // ignore "class"

   // non-class type ==> failure
   if (type == "const")
      file1 >> type;

   if (type != "class" && type != "struct") {
      type = "";                // empty return string indicates failure.
      goto cleanup;             //* RETURN *//
   }
   // ignore ' '
   c = file1.get();
   IfDebug(cerr << (char) c << flush);

   // this is what we want
   type.ReadToDelim(file1, ')');
   IfDebug(cerr << type << endl);

   // new version of CINT returns: "class TClassName*const)0x12345"
   // so we have to strip off "const"
   if (type.EndsWith("const"))
      type.Remove(type.Length() - 5);

 cleanup:
   // done reading from file
   file1.close();
   gSystem->Unlink(tmpfile);

   return type;
}

Bool_t TTabCom::ExcludedByFignore(TString s)
{
   //[static utility function]/////////////////////////////
   //
   //  returns true iff "s" ends with one of
   //  the strings listed in the "TabCom.FileIgnore" resource.
   //
   /////////////////////////////////////////////////////////////

   const char *fignore = gEnv->GetValue("TabCom.FileIgnore", (char *) 0);

   if (!fignore) {
      return kFALSE;
   } else {
#ifdef R__SSTREAM
      istringstream endings((char *) fignore);
#else
      istrstream endings((char *) fignore);	// do i need to make a copy first?
#endif
      TString ending;

      ending.ReadToDelim(endings, kDelim);

      while (!ending.IsNull()) {
         if (s.EndsWith(ending))
            return kTRUE;
         else
            ending.ReadToDelim(endings, kDelim);	// next
      }
      return kFALSE;
   }
}
TString TTabCom::GetSysIncludePath(void)
{
   //[static utility function]/////////////////////////////
   //
   //  returns a colon-separated string of directories
   //  that CINT will search when you call #include<...>
   //
   //  returns empty string on failure.
   //
   ///////////////////////////////////////////////////////////

   // >i noticed that .include doesn't list the standard directories like
   // >/usr/include or /usr/include/CC.
   // >
   // >how can i get a list of all the directories the interpreter will
   // >search through when the user does a #include<...> ?
   //
   // Right now, there is no easy command to tell you about it.  Instead, I can
   // describe it here.
   //
   // 1) CINT first searches current working directory for #include "xxx"
   //   (#include <xxx> does not)
   //
   // 2) CINT searches include path directories given by -I option
   //
   // 3) CINT searches following standard include directories.
   //    $CINTSYSDIR/include
   //    $CINTSYSDIR/stl
   //    $CINTSYSDIR/msdev/include   if VC++4.0
   //    $CINTSYSDIR/sc/include      if Symantec C++
   //    /usr/include
   //    /usr/include/g++            if gcc,g++
   //    /usr/include/CC             if HP-UX
   //    /usr/include/codelibs       if HP-UX
   //
   // .include command only displays 2).
   //
   // Thank you
   // Masaharu Goto

   // 1) current dir
   // ----------------------------------------------
   // N/A


   // 2) -I option (and #pragma includepath)
   // ----------------------------------------------

   // get this part of the include path from the interpreter
   // and stick it in a tmp file.
   const char *tmpfilename = tmpnam(0);

   FILE *fout = fopen(tmpfilename, "w");
   if (!fout) return "";
   G__display_includepath(fout);
   fclose(fout);

   // open the tmp file
   ifstream file1(tmpfilename);
   if (!file1) {                // error
      Error("TTabCom::GetSysIncludePath", "could not open file \"%s\"",
            tmpfilename);
      gSystem->Unlink(tmpfilename);
      return "";
   }
   // parse it.
   TString token;               // input buffer
   TString path;                // all directories so far (colon-separated)
   file1 >> token;              // skip "include"
   file1 >> token;              // skip "path:"
   while (file1) {
      file1 >> token;
      if (!token.IsNull()) {
         if (path.Length() > 0)
            path.Append(":");
         path.Append(token.Data() + 2);	// +2 skips "-I"
      }
   }

   // done with the tmp file
   file1.close();
   gSystem->Unlink(tmpfilename);

   // 3) standard directories
   // ----------------------------------------------

#ifndef CINTINCDIR
   TString CINTSYSDIR("$ROOTSYS/cint");
#else
   TString CINTSYSDIR(CINTINCDIR);
#endif
   path.Append(":" + CINTSYSDIR + "/include");
//   path.Append(":"+CINTSYSDIR+"/stl");
//   path.Append(":"+CINTSYSDIR+"/msdev/include");
//   path.Append(":"+CINTSYSDIR+"/sc/include");
   path.Append(":/usr/include");
//   path.Append(":/usr/include/g++");
//   path.Append(":/usr/include/CC");
//   path.Append(":/usr/include/codelibs");

   return path;
}

Bool_t TTabCom::IsDirectory(const char fileName[])
{
   //[static utility function]/////////////////////////////
   //
   //  calls TSystem::GetPathInfo() to see if "fileName"
   //  is a system directory.
   //
   ///////////////////////////////////////////////////////

   Long_t flags = 0;
   gSystem->GetPathInfo(fileName, 0, (Long_t*)0, &flags, 0);
   return (int) flags & 2;
}
TSeqCol *TTabCom::NewListOfFilesInPath(const char path1[])
{
   //[static utility function]/////////////////////////////
   //
   //  creates a list containing the full path name for each file
   //  in the (colon separated) string "path1"
   //
   //  memory is allocated with "new", so
   //  whoever calls this function takes responsibility for deleting it.
   //
   //////////////////////////////////////////////////////////////////////

   assert(path1 != 0);

   TContainer *pList = new TContainer;	// maybe use RTTI here? (since its a static function)
#ifdef R__SSTREAM
   istringstream path((char *) path1);
#else
   istrstream path((char *) path1);
#endif
   TString dirName;

   dirName.ReadToDelim(path, kDelim);

   while (!dirName.IsNull()) {
      IfDebug(cerr << "NewListOfFilesInPath(): dirName = " << dirName <<
              endl);

      AppendListOfFilesInDirectory(dirName, pList);

      // next
      dirName.ReadToDelim(path, kDelim);
   }

   return pList;
}

Bool_t TTabCom::PathIsSpecifiedInFileName(const TString & fileName)
{
   //[static utility function]/////////////////////////////
   //
   //  true if "fileName"
   //  1. is an absolute path ("/tmp/a")
   //  2. is a relative path  ("../whatever", "./test")
   //  3. starts with user name ("~/mail")
   //  4. starts with an environment variable ("$ROOTSYS/bin")
   //
   //////////////////////////////////////////////////////////////////////////

   char c1 = (fileName.Length() > 0) ? fileName[0] : 0;
   return c1 == '/' || c1 == '~' || c1 == '$' || fileName.BeginsWith("./")
       || fileName.BeginsWith("../");
}

void TTabCom::NoMsg(Int_t errorLevel)
{
   //[static utility function]/////////////////////////////
   //
   //  calling "NoMsg( errorLevel )",
   //  sets "gErrorIgnoreLevel" to "errorLevel+1" so that
   //  all errors with "level < errorLevel" will be ignored.
   //
   //  calling the function with a negative argument
   //  (e.g., "NoMsg( -1 )")
   //  resets gErrorIgnoreLevel to its previous value.
   //
   //////////////////////////////////////////////////////////////////

   ////////////////////////////////////////////////////////////////
   //
   // if you call the function twice with a non-negative argument
   // (without an intervening call with a negative argument)
   // it will complain because it is almost certainly an error
   // that will cause the function to loose track of the previous
   // value of gErrorIgnoreLevel.
   //
   // most common causes: 1. suspiciously placed "return;" statement
   //                     2. calling a function that calls "NoMsg()"
   //
   //////////////////////////////////////////////////////////////////

   const Int_t kNotDefined = -1;
   static Int_t old_level = kNotDefined;

   if (errorLevel < 0)          // reset
   {
      if (old_level == kNotDefined) {
         cerr << "NoMsg(): ERROR 1. old_level==" << old_level << endl;
         return;
      }

      gErrorIgnoreLevel = old_level;	// resore
      old_level = kNotDefined;
   } else                       // set
   {
      if (old_level != kNotDefined) {
         cerr << "NoMsg(): ERROR 2. old_level==" << old_level << endl;
         return;
      }

      old_level = gErrorIgnoreLevel;
      if (gErrorIgnoreLevel <= errorLevel)
         gErrorIgnoreLevel = errorLevel + 1;
   }
}

//
//                           static utility funcitons
//
// ----------------------------------------------------------------------------


// ----------------------------------------------------------------------------
//
//                       private member functions
//
//


Int_t TTabCom::Complete(const TRegexp & re,
                        const TSeqCol * pListOfCandidates,
                        const char appendage[])
{
   // [private]

   // returns position of first change in buffer
   // ------------------------------------------
   // -2 ==> new line altogether (whole thing needs to be redrawn, including prompt)
   // -1 ==> no changes
   //  0 ==> beginning of line
   //  1 ==> after 1st char
   //  n ==> after nth char

   IfDebug(cerr << "TTabCom::Complete() ..." << endl);
   assert(fpLoc != 0);
   assert(pListOfCandidates != 0);

   Int_t pos;                   // position of first change
   const int loc = *fpLoc;      // location where TAB was pressed

   // -----------------------------------------
   //
   // 1. get the substring we need to complete
   //
   // NOTES:
   // s1 = original buffer
   // s2 = sub-buffer from 0 to wherever the user hit TAB
   // s3 = the actual text that needs completing
   //
   // -----------------------------------------
   TString s1(fBuf);
   TString s2 = s1(0, loc);
   TString s3 = s2(re);

   int start = s2.Index(re);

   IfDebug(cerr << "   s1: " << s1 << endl);
   IfDebug(cerr << "   s2: " << s2 << endl);
   IfDebug(cerr << "   s3: " << s3 << endl);
   IfDebug(cerr << "start: " << start << endl);
   IfDebug(cerr << endl);

   // -----------------------------------------
   // 2. go through each possible completion,
   //    keeping track of the number of matches
   // -----------------------------------------
   TList listOfMatches;         // list of matches (local filenames only) (insertion order must agree across these 3 lists)
   TList listOfFullPaths;       // list of matches (full filenames)       (insertion order must agree across these 3 lists)

   int nMatches = 0;            // number of matches
   TObject *pObj;               // pointer returned by iterator
   TIter next_candidate(pListOfCandidates);
   TIter next_match(&listOfMatches);
   TIter next_fullpath(&listOfFullPaths);

   // stick all matches into "listOfMatches"
   while ((pObj = next_candidate())) {
      // get the full filename
      const char *s4 = pObj->GetName();

      assert(s4 != 0);

      // pick off tail
      const char *s5 = strrchr(s4, '/');
      if (!s5)
         s5 = s4;               // no '/' found
      else
         s5 += 1;               // advance past '/'

      // check for match
      if (strstr(s5, s3) == s5) {
         nMatches += 1;
         listOfMatches.Add(new TObjString(s5));
         listOfFullPaths.Add(new TObjString(s4));
         IfDebug(cerr << "adding " << s5 << '\t' << s4 << endl);
      } else {
         IfDebug(cerr << "considered " << s5 << '\t' << s4 << endl);
      }
   }

   // -----------------------------------------
   // 3. beep, list, or complete
   //    depending on how many matches were found
   // -----------------------------------------

   // 3a. no matches ==> bell
   TString partialMatch = "";

   if (nMatches == 0) {
      cout << "\a" << flush;
      pos = -1;
      goto done;                //* RETURN *//
   }
   // 3b. one or more matches.
   char match[1024];

   if (nMatches == 1) {
      // get the (lone) match
      const char *short_name = next_match()->GetName();
      const char *full_name = next_fullpath()->GetName();

      pObj = pListOfCandidates->FindObject(short_name);
      if (pObj) {
         IfDebug(cerr << endl << "class: " << pObj->ClassName() << endl);
         TString className = pObj->ClassName();
         if (0);
         else if (className == "TMethod" || className == "TFunction") {
            TFunction *pFunc = (TFunction *) pObj;
            if (pFunc->GetNargsOpt() == pFunc->GetNargs())
               appendage = "()";	// all args have default values
            else
               appendage = "("; // user needs to supply some args
         } else if (className == "TDataMember") {
            appendage = " ";
         }
      }

      CopyMatch(match, short_name, appendage, full_name);
   } else {
      // multiple matches ==> complete as far as possible
      Char_t ch;
      Int_t nGoodStrings;

      for (int i = 0;
           (ch = AllAgreeOnChar(i, &listOfMatches, nGoodStrings));
           i += 1) {
         IfDebug(cerr << " i=" << i << " ch=" << ch << endl);
         partialMatch.Append(ch);
      }

      const char *s;
      const char *s0;

      // multiple matches, but maybe only 1 of them is any good.
      if (nGoodStrings == 1) {

         // find the 1 good match
         do {
            s = next_match()->GetName();
            s0 = next_fullpath()->GetName();
         }
         while (ExcludedByFignore(s));

         // and use it.
         CopyMatch(match, s, appendage, s0);
      } else {
         IfDebug(cerr << "more than 1 GoodString" << endl);

//             if( partialMatch.Length() > (int)strlen(s3) )
         if (partialMatch.Length() > s3.Length())
            // this partial match is our (partial) completion.
         {
            CopyMatch(match, partialMatch.Data());
         } else
            // couldn't do any completing at all,
            // print a list of all the ambiguous matches
            // (except for those excluded by "FileIgnore")
         {
            IfDebug(cerr << "printing ambiguous matches" << endl);
            cout << endl;
            while ((pObj = next_match())) {
               s = pObj->GetName();
               s0 = next_fullpath()->GetName();
               if (!ExcludedByFignore(s) || nGoodStrings == 0) {
                  if (IsDirectory(s0))
                     cout << s << "/" << endl;
                  else
                     cout << s << endl;
               }
            }
            pos = -2;
            goto done;          //* RETURN *//
         }
      }
   }


   // ---------------------------------------
   // 4. finally write text into the buffer.
   // ---------------------------------------
   {
      int i = strlen(fBuf);     // old EOL position is i
      int L = strlen(match) - (loc - start);	// new EOL position will be i+L

      // first check for overflow
      if (strlen(fBuf) + strlen(match) + 1 > BUF_SIZE) {
         Error("TTabCom::Complete", "buffer overflow");
         pos = -2;
         goto done;             /* RETURN */
      }
      // debugging output
      IfDebug(cerr << "  i=" << i << endl);
      IfDebug(cerr << "  L=" << L << endl);
      IfDebug(cerr << "loc=" << loc << endl);

      // slide everything (including the null terminator) over to make space
      for (; i >= loc; i -= 1) {
         fBuf[i + L] = fBuf[i];
      }

      // insert match
      strncpy(fBuf + start, match, strlen(match));

      pos = loc;                // position of first change in "fBuf"
      *fpLoc = loc + L;         // new cursor position
   }

 done:                         // <----- goto label
   // un-init
   fpLoc = 0;
   fBuf = 0;

   return pos;
}
void TTabCom::CopyMatch(char dest[], const char localName[],
                        const char appendage[],
                        const char fullName[]) const
{
   // [private]

   // if "appendage" is NULL, no appendage is applied.
   //
   // if "appendage" is of the form "filenameXXX" then,
   // "filename" is ignored and "XXX" is taken to be the appendage,
   // but it will only be applied if the file is not a directory...
   // if the file is a directory, a "/" will be used for the appendage instead.
   //
   // if "appendage" is of the form "XXX" then "XXX" will be appended to the match.

   assert(dest != 0);
   assert(localName != 0);

   // potential buffer overflow.
   strcpy(dest, localName);

   const char *key = "filename";
   const int key_len = strlen(key);

   IfDebug(cerr << "CopyMatch()." << endl);
   IfDebug(cerr << "localName: " << (localName ? localName : "NULL") <<
           endl);
   IfDebug(cerr << "appendage: " << (appendage ? appendage : "NULL") <<
           endl);
   IfDebug(cerr << " fullName: " << (fullName ? fullName : "NULL") <<
           endl);


   // check to see if "appendage" starts with "key"
   if (appendage && strncmp(appendage, key, key_len) == 0) {
      // filenames get special treatment
      appendage += key_len;
      IfDebug(cerr << "new appendage: " << appendage << endl);
      if (IsDirectory(fullName)) {
         if (fullName)
            strcpy(dest + strlen(localName), "/");
      } else {
         if (appendage)
            strcpy(dest + strlen(localName), appendage);
      }
   } else {
      if (appendage)
         strcpy(dest + strlen(localName), appendage);
   }
}

TTabCom::EContext_t TTabCom::DetermineContext() const
{
   // [private]

   assert(fBuf != 0);

   const char *pStart;          // start of match
   const char *pEnd;            // end of match

   for (int context = 0; context < kNUM_PAT; ++context) {
      pEnd = Matchs(fBuf, *fpLoc, fPat[context], &pStart);
      if (pEnd) {
         IfDebug(cerr << endl
                 << "context=" << context << " "
                 << "RegExp=" << fRegExp[context]
                 << endl);
         return EContext_t(context);	//* RETURN *//
      }
   }

   return kUNKNOWN_CONTEXT;     //* RETURN *//
}

TString TTabCom::DeterminePath(const TString & fileName,
                               const char defaultPath[]) const
{
   // [private]

   if (PathIsSpecifiedInFileName(fileName)) {
      TString path = fileName;
      gSystem->ExpandPathName(path);
      path = gSystem->DirName(path);

      return path;
   } else {
      TString newBase;
      TString extendedPath;
      if (fileName.Contains("/")) {
         newBase = gSystem->DirName(fileName);
         extendedPath = ExtendPath(defaultPath, newBase);
      } else {
         newBase = "";
         extendedPath = defaultPath;
      }
      IfDebug(cerr << endl);
      IfDebug(cerr << "    fileName: " << fileName << endl);
      IfDebug(cerr << "    pathBase: " << newBase << endl);
      IfDebug(cerr << " defaultPath: " << defaultPath << endl);
      IfDebug(cerr << "extendedPath: " << extendedPath << endl);
      IfDebug(cerr << endl);

      return extendedPath;
   }
}
TString TTabCom::ExtendPath(const char originalPath[], TString newBase) const
{
   // [private]

   if (newBase.BeginsWith("/"))
      newBase = newBase.Strip(TString::kLeading, '/');
#ifdef R__SSTREAM
   stringstream str;
#else
   strstream str;
#endif
   TString dir;
   TString newPath;
   str << originalPath;

   while (str.good())
   {
      dir = "";
      dir.ReadToDelim(str, kDelim);
      if (dir.IsNull())
         continue;              // ignore blank entries
      newPath.Append(dir);
      if (!newPath.EndsWith("/"))
         newPath.Append("/");
      newPath.Append(newBase);
      newPath.Append(kDelim);
   }

   return newPath.Strip(TString::kTrailing, kDelim);
}

Int_t TTabCom::Hook(char *buf, int *pLoc)
{
   // [private]

   // initialize
   fBuf = buf;
   fpLoc = pLoc;

   // default
   Int_t pos = -2;              // position of the first character that was changed in the buffer (needed for redrawing)

   // get the context this tab was triggered in.
   EContext_t context = DetermineContext();

   // get the substring that triggered this tab (as defined by "SetPattern()")
   const char dummy[] = ".";
   TRegexp re1(context == kUNKNOWN_CONTEXT ? dummy : fRegExp[context]);
   TString s1(fBuf);
   TString s2 = s1(0, *fpLoc);
   TString s3 = s2(re1);

   switch (context) {
   case kUNKNOWN_CONTEXT:
      cerr << endl << "tab completion not implemented for this context" <<
          endl;
      pos = -2;
      break;

   case kSYS_UserName:
      {
         const TSeqCol *pListOfUsers = GetListOfUsers();

         pos = Complete("[^~]*$", pListOfUsers, "/");
      }
      break;
   case kSYS_EnvVar:
      {
         const TSeqCol *pEnv = GetListOfEnvVars();

         pos = Complete("[^$]*$", pEnv, "");
      }
      break;

   case kCINT_stdout:
   case kCINT_stderr:
   case kCINT_stdin:
      {
         auto TString fileName = s3("[^ ><]*$");
         gSystem->ExpandPathName(fileName);
         const TString filePath = gSystem->DirName(fileName);
         const TSeqCol *pListOfFiles =
             GetListOfFilesInPath(filePath.Data());

//             pos = Complete( "[^ /]*$", pListOfFiles, " " );
         pos = Complete("[^ /]*$", pListOfFiles, "filename ");
      }
      break;

   case kCINT_Edit:
   case kCINT_Load:
   case kCINT_Exec:
   case kCINT_EXec:
      {
         const TString fileName = s3("[^ ]*$");
         const TString macroPath =
             DeterminePath(fileName, TROOT::GetMacroPath());
         const TSeqCol *pListOfFiles =
             GetListOfFilesInPath(macroPath.Data());

//             pos = Complete( "[^ /]*$", pListOfFiles, " " );
         pos = Complete("[^ /]*$", pListOfFiles, "filename ");
      }
      break;

   case kCINT_pragma:
      {
         pos = Complete("[^ ]*$", GetListOfPragmas(), "");
      }
      break;
   case kCINT_includeSYS:
      {
         TString fileName = s3("[^<]*$");
         if (PathIsSpecifiedInFileName(fileName) || fileName.Contains("/")) {
            TString includePath =
                DeterminePath(fileName, GetSysIncludePath());

//                  pos = Complete( "[^</]*$", GetListOfFilesInPath( includePath ), "> " );
            pos =
                Complete("[^</]*$", GetListOfFilesInPath(includePath),
                         "filename> ");
         } else {
//                  pos = Complete( "[^</]*$", GetListOfSysIncFiles(), "> " );
            pos =
                Complete("[^</]*$", GetListOfSysIncFiles(), "filename> ");
         }
      }
      break;
   case kCINT_includePWD:
      {
         const TString fileName = s3("[^\"]*$");
         const TString includePath = DeterminePath(fileName, ".");
         const TSeqCol *pListOfFiles =
             GetListOfFilesInPath(includePath.Data());

//             pos = Complete( "[^\"/]*$", pListOfFiles, "\" " );
         pos = Complete("[^\"/]*$", pListOfFiles, "filename\" ");
      }
      break;

   case kCINT_cpp:
      {
         pos = Complete("[^# ]*$", GetListOfCppDirectives(), " ");
      }
      break;

   case kROOT_Load:
      {
         const TString fileName = s3("[^\"]*$");
//             const TString  dynamicPath  = DeterminePath( fileName, TROOT::GetDynamicPath() ); /* should use this one */
         const TString dynamicPath = DeterminePath(fileName,
                                                   gEnv->
                                                   GetValue
                                                   ("Root.DynamicPath",
                                                    (char *) 0));
         const TSeqCol *pListOfFiles = GetListOfFilesInPath(dynamicPath);

//             pos = Complete( "[^\"/]*$", pListOfFiles, "\");" );
         pos = Complete("[^\"/]*$", pListOfFiles, "filename\");");
      }
      break;

   case kSYS_FileName:
      {
         auto TString fileName = s3("[^ \"]*$");
         gSystem->ExpandPathName(fileName);
         const TString filePath = gSystem->DirName(fileName);
         const TSeqCol *pListOfFiles =
             GetListOfFilesInPath(filePath.Data());

//             pos = Complete( "[^\" /]*$", pListOfFiles, "\"" );
         pos = Complete("[^\" /]*$", pListOfFiles, "filename\"");
      }
      break;

   case kCXX_ScopeMember:
      {
         const EContext_t original_context = context;	// save this for later

         TClass *pClass;
         TString name = s3("^[_a-zA-Z][_a-zA-Z0-9]*");	// may be a class, object, or pointer

         IfDebug(cerr << endl);
         IfDebug(cerr << "name: " << '"' << name << '"' << endl);

         // We need to decompose s3 a little more:
         // The part name is the partial symbol at the end of ::
         // eg. given s3 = "foo::bar::part" ,  partname = "part"
         TString partname = s3("[_a-zA-Z][_a-zA-Z0-9]*$");

         // The prefix, considering the s3 = "foo::bar::part" example would be
         // prefix = "foo::bar::". prefix equals the empty string if there is only one
         // or no set of colons in s3.
         // Note: we reconstruct the fully qualified name with a while loop because
         // it does not seem that TRegexp can handle something like "([_a-zA-Z][_a-zA-Z0-9]*::)+$"
         TString prefix = "";
         TString str = s2;
         str.Remove(str.Length() - partname.Length(), partname.Length());
         while (1) {
            TString sym = str("[_a-zA-Z][_a-zA-Z0-9]*::$");
            if (sym.Length() == 0)
               break;
            str.Remove(str.Length() - sym.Length(), sym.Length());
            prefix = sym + prefix;
         }

         // Not the preprefix would be = "foo::" from our previous example or the empty
         // string, "" if there is only one or no set of colons in prefix, eg. prefix = "bar::"
         TString preprefix = prefix;
         TString sym = prefix("[_a-zA-Z][_a-zA-Z0-9]*::$");
         preprefix.Remove(preprefix.Length() - sym.Length(), sym.Length());

         IfDebug(cerr << "prefix: " << '"' << prefix << '"' << endl);
         IfDebug(cerr << "preprefix: " << '"' << preprefix << '"' << endl);

         // Sometimes, eg on startup of ROOT fpNamespaces might be NULL,
         // so create and fill the array.
         if (fpNamespaces == NULL)
            RehashClasses();

         TString namesp = prefix;
         if (namesp.Length() >= 2)
            namesp.Remove(namesp.Length() - 2, 2);	// Remove the '::' at the end of the string.
         IfDebug(cerr << "namesp: " << '"' << namesp << '"' << endl);

         // Try find the namesp string in the list of namespaces. If its found then
         // we need to treat the different prefices a little differently:
         TObjString objstr(namesp);
         TObjString *foundstr =
             (TObjString *) fpNamespaces->FindObject(&objstr);
         if (foundstr != NULL) {
            TContainer *pList = new TContainer;

            // Add all classes to pList that contain the prefix, i.e. are in the
            // specified namespace.
            const TSeqCol *tmp = GetListOfClasses();
            if (!tmp) break;

            Int_t i;
            for (i = 0; i < tmp->GetSize(); i++) {
               TString str = ((TObjString *) tmp->At(i))->String();
               TString rxp = "^";
               rxp += prefix;
               if (str.Contains(TRegexp(rxp))) {
                  str.Remove(0, prefix.Length());
                  TString s = str("^[^: ]*");
                  TObjString *ostr = new TObjString(s);
                  if (!pList->Contains(ostr))
                     pList->Add(ostr);
                  else
                     delete ostr;
               }
            }
            // Add all the sub-namespaces in the specified namespace.
            for (i = 0; i < fpNamespaces->GetSize(); i++) {
               TString str =
                   ((TObjString *) fpNamespaces->At(i))->String();
               TString rxp = "^";
               rxp += prefix;
               if (str.Contains(TRegexp(rxp))) {
                  str.Remove(0, prefix.Length());
                  TString s = str("^[^: ]*");
                  TObjString *ostr = new TObjString(s);
                  if (!pList->Contains(ostr))
                     pList->Add(ostr);
                  else
                     delete ostr;
               }
            }

            // If a class with the same name as the Namespace name exists then
            // add it to the pList. (I don't think the C++ spec allows for this
            // but do this anyway, cant harm).
            pClass = TryMakeClassFromClassName(preprefix + name);
            if (pClass != NULL) {
               pList->AddAll(pClass->GetListOfAllPublicMethods());
               pList->AddAll(pClass->GetListOfAllPublicDataMembers());
            }

            pos = Complete("[^: ]*$", pList, "");

            delete pList;
            if (pClass != NULL)
               delete pClass;
         } else {
            pClass = MakeClassFromClassName(preprefix + name);
            if (!pClass) {
               pos = -2;
               break;
            }

            TContainer *pList = new TContainer;

            pList->AddAll(pClass->GetListOfAllPublicMethods());
            pList->AddAll(pClass->GetListOfAllPublicDataMembers());

            pos = Complete("[^: ]*$", pList, "(");

            delete pList;
            delete pClass;
         }

         if (context != original_context)
            pos = -2;
      }
      break;

   case kCXX_DirectMember:
   case kCXX_IndirectMember:
      {
         const EContext_t original_context = context;	// save this for later

         TClass *pClass;
         TString name = s3("^[_a-zA-Z][_a-zA-Z0-9]*");	// may be a class, object, or pointer

         IfDebug(cerr << endl);
         IfDebug(cerr << "name: " << '"' << name << '"' << endl);

         switch (context) {
         case kCXX_DirectMember:
            pClass = MakeClassFromVarName(name, context);
            break;
         case kCXX_IndirectMember:
            pClass = MakeClassFromVarName(name, context);
            break;
         default:
            assert(0);
            break;
         }
         if (!pClass) {
            pos = -2;
            break;
         }

         TContainer *pList = new TContainer;

         pList->AddAll(pClass->GetListOfAllPublicMethods());
         pList->AddAll(pClass->GetListOfAllPublicDataMembers());

         switch (context) {
         case kCXX_DirectMember:
            pos = Complete("[^. ]*$", pList, "(");
            break;
         case kCXX_IndirectMember:
            pos = Complete("[^> ]*$", pList, "(");
            break;
         default:
            assert(0);
            break;
         }

         delete pList;
         delete pClass;

         if (context != original_context)
            pos = -2;
      }
      break;

   case kCXX_ScopeProto:
      {
         const EContext_t original_context = context;	// save this for later

         // get class
         TClass *pClass;
         TString name = s3("^[_a-zA-Z][_a-zA-Z0-9]*");
         // "name" may now be the name of a class, object, or pointer

         IfDebug(cerr << endl);
         IfDebug(cerr << "name: " << '"' << name << '"' << endl);

         // We need to decompose s3 a little more:
         // The partname is the method symbol and a bracket at the end of ::
         // eg. given s3 = "foo::bar::part(" ,  partname = "part("
         TString partname = s3("[_a-zA-Z][_a-zA-Z0-9]* *($");

         // The prefix, considering the s3 = "foo::bar::part" example would be
         // prefix = "foo::bar::". prefix equals the empty string if there is only one
         // or no set of colons in s3.
         // Note: we reconstruct the fully qualified name with a while loop because
         // it does not seem that TRegexp can handle something like "([_a-zA-Z][_a-zA-Z0-9]*::)+$"
         TString prefix = "";
         TString str = s2;
         str.Remove(str.Length() - partname.Length(), partname.Length());
         while (1) {
            TString sym = str("[_a-zA-Z][_a-zA-Z0-9]*::$");
            if (sym.Length() == 0)
               break;
            str.Remove(str.Length() - sym.Length(), sym.Length());
            prefix = sym + prefix;
         }

         // Not the preprefix would be = "foo::" from our previous example or the empty
         // string, "" if there is only one or no set of colons in prefix, eg. prefix = "bar::"
         TString preprefix = prefix;
         TString sym = prefix("[_a-zA-Z][_a-zA-Z0-9]*::$");
         preprefix.Remove(preprefix.Length() - sym.Length(), sym.Length());

         IfDebug(cerr << "prefix: " << '"' << prefix << '"' << endl);
         IfDebug(cerr << "preprefix: " << '"' << preprefix << '"' << endl);

         pClass = MakeClassFromClassName(preprefix + name);
         if (!pClass) {
            pos = -2;
            break;
         }
         // get method name
         TString methodName;

         // (normal member function)
         methodName = s3("[^:>\\.(]*($");
         methodName.Chop();
//          methodName.Remove( TString::kTrailing, ' ' ); // fons: don't you think this would be nice?
         methodName = methodName.Strip(TString::kTrailing, ' ');

         IfDebug(cerr << methodName << endl);

         // get methods
         TContainer *pList = new TContainer;
         pList->AddAll(pClass->GetListOfAllPublicMethods());

         // print prototypes
         Bool_t foundOne = kFALSE;
         TIter nextMethod(pList);
         TMethod *pMethod;
         while ((pMethod = (TMethod *) nextMethod())) {
            if (methodName == pMethod->GetName()) {
               foundOne = kTRUE;
               cout << endl << pMethod->GetReturnTypeName()
                   << " " << pMethod->GetName()
                   << pMethod->GetSignature();
               const char *comment = pMethod->GetCommentString();
               if (comment && comment[0] != '\0') {
                  cout << " \t// " << comment;
               }
            }
         }

         // done
         if (foundOne) {
            cout << endl;
            pos = -2;
         } else {
            cout << "\a" << flush;
            pos = -1;
         }

         // cleanup
         delete pList;
         delete pClass;

         if (context != original_context)
            pos = -2;
      }
      break;

   case kCXX_DirectProto:
   case kCXX_IndirectProto:
   case kCXX_NewProto:
   case kCXX_ConstructorProto:
      {
         const EContext_t original_context = context;	// save this for later

         // get class
         TClass *pClass;
         TString name;
         if (context == kCXX_NewProto) {
            name = s3("[_a-zA-Z][_a-zA-Z0-9]* *($", 3);
            name.Chop();
//                  name.Remove( TString::kTrailing, ' ' ); // fons: don't you think this would be nice?
            name = name.Strip(TString::kTrailing, ' ');
            // "name" should now be the name of a class
         } else {
            name = s3("^[_a-zA-Z][_a-zA-Z0-9]*");
            // "name" may now be the name of a class, object, or pointer
         }
         IfDebug(cerr << endl);
         IfDebug(cerr << "name: " << '"' << name << '"' << endl);
         switch (context) {
         case kCXX_ScopeProto:
            pClass = MakeClassFromClassName(name);
            break;
         case kCXX_DirectProto:
            pClass = MakeClassFromVarName(name, context);
            break;
         case kCXX_IndirectProto:
            pClass = MakeClassFromVarName(name, context);
            break;
         case kCXX_NewProto:
            pClass = MakeClassFromClassName(name);
            break;
         case kCXX_ConstructorProto:
            pClass = MakeClassFromClassName(name);
            break;
         default:
            assert(0);
            break;
         }
         if (!pClass) {
            pos = -2;
            break;
         }
         // get method name
         TString methodName;
         if (context == kCXX_ConstructorProto || context == kCXX_NewProto) {
            // (constructor)
            methodName = name;
         } else {
            // (normal member function)
            methodName = s3("[^:>\\.(]*($");
            methodName.Chop();
//                  methodName.Remove( TString::kTrailing, ' ' ); // fons: don't you think this would be nice?
            methodName = methodName.Strip(TString::kTrailing, ' ');
         }
         IfDebug(cerr << methodName << endl);

         // get methods
         TContainer *pList = new TContainer;
         pList->AddAll(pClass->GetListOfAllPublicMethods());

         // print prototypes
         Bool_t foundOne = kFALSE;
         TIter nextMethod(pList);
         TMethod *pMethod;
         while ((pMethod = (TMethod *) nextMethod())) {
            if (methodName == pMethod->GetName()) {
               foundOne = kTRUE;
               cout << endl << pMethod->GetReturnTypeName()
                   << " " << pMethod->GetName()
                   << pMethod->GetSignature();
               const char *comment = pMethod->GetCommentString();
               if (comment && comment[0] != '\0') {
                  cout << " \t// " << comment;
               }
            }
         }

         // done
         if (foundOne) {
            cout << endl;
            pos = -2;
         } else {
            cout << "\a" << flush;
            pos = -1;
         }

         // cleanup
         delete pList;
         delete pClass;

         if (context != original_context)
            pos = -2;
      }
      break;

   case kCXX_Global:
      {
         // first need to veto a few possibilities.
         int L2 = s2.Length(), L3 = s3.Length();

         // "abc().whatever[TAB]"
         if (L2 > L3 && s2[L2 - L3 - 1] == '.') {
            cerr << endl <<
                "tab completion not implemented for this context" << endl;
            break;              // veto
         }
         // "abc()->whatever[TAB]"
         if (L2 > L3 + 1 && s2(L2 - L3 - 2, 2) == "->") {
            cerr << endl <<
                "tab completion not implemented for this context" << endl;
            break;              // veto
         }

         TContainer *pList = new TContainer;

         const TSeqCol *pL2 = GetListOfClasses();
         if (pL2) pList->AddAll(pL2);
         //
         const TSeqCol *pC1 = GetListOfGlobals();
         if (pC1) pList->AddAll(pC1);
         //
         const TSeqCol *pC3 = GetListOfGlobalFunctions();
         if (pC3) pList->AddAll(pC3);

         pos = Complete("[_a-zA-Z][_a-zA-Z0-9]*$", pList, "");

         delete pList;
      }
      break;

   case kCXX_GlobalProto:
      {
         // get function name
         TString functionName = s3("[_a-zA-Z][_a-zA-Z0-9]*");
         IfDebug(cerr << functionName << endl);

         TContainer listOfMatchingGlobalFuncs;
         TIter nextGlobalFunc(GetListOfGlobalFunctions());
         TObject *pObj;
         while ((pObj = nextGlobalFunc())) {
            if (strcmp(pObj->GetName(), functionName) == 0) {
               listOfMatchingGlobalFuncs.Add(pObj);
            }
         }

         if (listOfMatchingGlobalFuncs.IsEmpty()) {
            cerr << endl << "no such function: " << dblquote(functionName)
                << endl;
         } else {
            cout << endl;
            TIter next(&listOfMatchingGlobalFuncs);
            TFunction *pFunction;
            while ((pFunction = (TFunction *) next())) {
               cout << pFunction->GetReturnTypeName()
                   << " " << pFunction->GetName()
                   << pFunction->GetSignature()
                   << endl;
            }
         }

         pos = -2;
      }
      break;

          /******************************************************************/
      /*                                                                */
      /* default: should never happen                                   */
      /*                                                                */
          /******************************************************************/
   default:
      assert(0);
      break;
   }

   return pos;
}
void TTabCom::InitPatterns(void)
{
   // [private]

   // add more patterns somewhere below.
   // add corresponding enum to "EContext_t"
   //
   // note:
   // 1. in some cases order is important ...
   //
   //    the order of the "case" statements in "switch( context )" in "TTabCom::Hook()" is Not important.
   //
   //    the order of the "SetPattern()" function calls below is Not important.
   //
   //    the order of the initializers in the "EContext_t" enumeration Is important
   //    because DetermineContext() goes through the array in order, and returns at the first match.
   //
   // 2. below, "$" will match cursor position

   SetPattern(kSYS_UserName, "~[_a-zA-Z0-9]*$");
   SetPattern(kSYS_EnvVar, "$[_a-zA-Z0-9]*$");

   SetPattern(kCINT_stdout, "; *>>?.*$");	// stdout
   SetPattern(kCINT_stderr, "; *2>>?.*$");	// stderr
   SetPattern(kCINT_stdin, "; *<.*$");	// stdin

   SetPattern(kCINT_Edit, "^ *\\.E .*$");
   SetPattern(kCINT_Load, "^ *\\.L .*$");
   SetPattern(kCINT_Exec, "^ *\\.x +[-0-9_a-zA-Z~$./]*$");
   SetPattern(kCINT_EXec, "^ *\\.X +[-0-9_a-zA-Z~$./]*$");

   SetPattern(kCINT_pragma, "^# *pragma +[_a-zA-Z0-9]*$");
   SetPattern(kCINT_includeSYS, "^# *include *<[^>]*$");	// system files
   SetPattern(kCINT_includePWD, "^# *include *\"[^\"]*$");	// local files

   SetPattern(kCINT_cpp, "^# *[_a-zA-Z0-9]*$");

   SetPattern(kROOT_Load, "gSystem *-> *Load *( *\"[^\"]*$");

   SetPattern(kCXX_ScopeMember,
              "[_a-zA-Z][_a-zA-Z0-9]* *:: *[_a-zA-Z0-9]*$");
   SetPattern(kCXX_DirectMember,
              "[_a-zA-Z][_a-zA-Z0-9]* *\\. *[_a-zA-Z0-9]*$");
   SetPattern(kCXX_IndirectMember,
              "[_a-zA-Z][_a-zA-Z0-9]* *-> *[_a-zA-Z0-9]*$");

   SetPattern(kCXX_ScopeProto,
              "[_a-zA-Z][_a-zA-Z0-9]* *:: *[_a-zA-Z0-9]* *($");
   SetPattern(kCXX_DirectProto,
              "[_a-zA-Z][_a-zA-Z0-9]* *\\. *[_a-zA-Z0-9]* *($");
   SetPattern(kCXX_IndirectProto,
              "[_a-zA-Z][_a-zA-Z0-9]* *-> *[_a-zA-Z0-9]* *($");
   SetPattern(kCXX_NewProto, "new +[_a-zA-Z][_a-zA-Z0-9]* *($");
   SetPattern(kCXX_ConstructorProto,
              "[_a-zA-Z][_a-zA-Z0-9]* +[_a-zA-Z][_a-zA-Z0-9]* *($");

   SetPattern(kSYS_FileName, "\"[-0-9_a-zA-Z~$./]*$");
   SetPattern(kCXX_Global, "[_a-zA-Z][_a-zA-Z0-9]*$");
   SetPattern(kCXX_GlobalProto, "[_a-zA-Z][_a-zA-Z0-9]* *($");
}
TClass *TTabCom::MakeClassFromClassName(const char className[]) const
{
   // [private]
   //   (does some specific error handling that makes the function unsuitable for general use.)
   //   returns a new'd TClass given the name of a class.
   //   user must delete.
   //   returns 0 in case of error.

   // the TClass constructor will print a Warning message for classes that don't exist
   // so, ignore warnings temporarily.
   NoMsg(kWarning);
   TClass *pClass = new TClass(className);
   NoMsg(-1);

   // make sure "className" exists
   if (pClass->Size() == 0) {
      // i'm assuming this happens iff there was some error.
      // (misspelled the class name, for example)
      cerr << endl << "class " << dblquote(className) << " not defined." <<
          endl;
      return 0;
   }

   return pClass;
}


TClass *TTabCom::TryMakeClassFromClassName(const char className[]) const
{
   // Same as above but does not print the error message.

   // the TClass constructor will print a Warning message for classes that don't exist
   // so, ignore warnings temporarily.
   NoMsg(kWarning);
   TClass *pClass = new TClass(className);
   NoMsg(-1);

   // make sure "className" exists
   if (pClass->Size() == 0) {
      return 0;
   }

   return pClass;
}


TClass *TTabCom::MakeClassFromVarName(const char varName[],
                                      EContext_t & context)
{
   // [private]
   //   (does some specific error handling that makes the function unsuitable for general use.)
   //   returns a new'd TClass given the name of a variable.
   //   user must delete.
   //   returns 0 in case of error.
   //   if user has operator.() or operator->() bacwards, will modify: context, *fpLoc and fBuf.
   //   context sensitive behevior.


   // need to make sure "varName" exists
   // because "DetermineClass()" prints clumsy error message otherwise.
   Bool_t varName_exists = GetListOfGlobals()->Contains(varName) ||	// check in list of globals first.
       (gROOT->FindObject(varName) != 0);	// then check CINT "shortcut #3"

   // not found...
   if (!varName_exists) {
      cerr << endl << "variable " << dblquote(varName) << " not defined."
          << endl;
      return 0;                 //* RETURN *//
   }

     /*****************************************************************************************/
   /*                                                                                       */
   /*  this section is really ugly.                                                         */
   /*  and slow.                                                                            */
   /*  it could be made a lot better if there was some way to tell whether or not a given   */
   /*  variable is a pointer or a pointer to a pointer.                                     */
   /*                                                                                       */
     /*****************************************************************************************/

   TString className = DetermineClass(varName);

   if (className.Length() < 1) {
      // this will happen if "varName" is a fundamental type (as opposed to class type).
      // or a pointer to a pointer.
      // or a function pointer.
      cerr << endl << "problem determining class of " << dblquote(varName)
          << endl;
      return 0;                 //* RETURN *//
   }

   Bool_t varIsPointer = className[className.Length() - 1] == '*';

   if (varIsPointer &&
       (context == kCXX_DirectMember || context == kCXX_DirectProto)) {
      // user is using operator.() instead of operator->()
      // ==>
      //      1. we are in wrong context.
      //      2. user is lazy
      //      3. or maybe confused

      // 1. fix the context
      switch (context) {
      case kCXX_DirectMember:
         context = kCXX_IndirectMember;
         break;
      case kCXX_DirectProto:
         context = kCXX_IndirectProto;
         break;
      default:
         assert(0);
         break;
      }

      // 2. fix the operator.
      int i;
      for (i = *fpLoc; fBuf[i] != '.'; i -= 1) {
      }
      int loc = i;
      for (i = strlen(fBuf); i >= loc; i -= 1) {
         fBuf[i + 1] = fBuf[i];
      }
      fBuf[loc] = '-';
      fBuf[loc + 1] = '>';
      *fpLoc += 1;

      // 3. inform the user.
      cerr << endl << dblquote(varName) <<
          " is of pointer type. Use this operator: ->" << endl;
   }

   if (context == kCXX_IndirectMember || context == kCXX_IndirectProto) {
      if (varIsPointer) {
         className.Chop();      // remove the '*'

         if (className[className.Length() - 1] == '*') {
            cerr << endl << "can't handle pointers to pointers." << endl;
            return 0;           //* RETURN *//
         }
      } else {
         // user is using operator->() instead of operator.()
         // ==>
         //      1. we are in wrong context.
         //      2. user is lazy
         //      3. or maybe confused

         // 1. fix the context
         switch (context) {
         case kCXX_IndirectMember:
            context = kCXX_DirectMember;
            break;
         case kCXX_IndirectProto:
            context = kCXX_DirectProto;
            break;
         default:
            assert(0);
            break;
         }

         // 2. fix the operator.
         int i;
         for (i = *fpLoc; fBuf[i - 1] != '-' && fBuf[i] != '>'; i -= 1) {
         }
         fBuf[i - 1] = '.';
         int len = strlen(fBuf);
         for (; i < len; i += 1) {
            fBuf[i] = fBuf[i + 1];
         }
         *fpLoc -= 1;

         // 3. inform the user.
         cerr << endl << dblquote(varName) <<
             " is not of pointer type. Use this operator: ." << endl;
      }
   }

   return new TClass(className);
}
void TTabCom::SetPattern(EContext_t handle, const char regexp[])
{
   // [private]

   // prevent overflow
   if (handle >= kNUM_PAT) {
      cerr << endl
          << "ERROR: handle="
          << (int) handle << " >= kNUM_PAT=" << (int) kNUM_PAT << endl;
      return;
   }

   fRegExp[handle] = regexp;
   Makepat(regexp, fPat[handle], MAX_LEN_PAT);
}
