// @(#)root/rint:$Name:  $:$Id: TTabCom.cxx,v 1.2 2000/07/12 18:51:05 rdm Exp $
// Author: Christian Lacunza <lacunza@cdfsg6.lbl.gov>   27/04/99

/*************************************************************************
 * 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)                                              //
//                                                                        //
////////////////////////////////////////////////////////////////////////////

#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"

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


#include <stdio.h>
#include <iostream.h>
#ifndef WIN32
#  include <strstream.h>
#else
#  include <strstrea.h>
#endif
#include <fstream.h>
#include <iomanip.h>     // setw()


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


ClassImp(TTabCom)

// ----------------------------------------------------------------------------
//
//             global/file scope variables
//

TTabCom* gTabCom=0;


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;
     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;     }
 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 TSeqCollection* TTabCom::GetListOfClasses( void )
{
     if( !fpClasses )
     {
          // generate a text list of classes on disk
          strstream   cmd;
          const char* tmpfilename = tmpnam(0);
          cmd << ".class > " << tmpfilename << endl;
          gROOT->ProcessLineSync( cmd.str() ); // memory leak cmd.str()

          // 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;
          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;
               if(0);
               else if ((index=line.Index(" class ")    ) >= 0) line = line(1+index+6, 32000);
               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("[^ ]*");
               fpClasses->Add( new TObjString(line.Data()) );
          }

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

     return fpClasses;
}
 const TSeqCollection* 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 TSeqCollection* 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 TSeqCollection* TTabCom::GetListOfEnvVars()
{
     // calls "/bin/env"

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

#ifndef WIN32
         cmd << "/bin/env > " << tmpfilename << endl;
#else
         cmd << "set > " << tmpfilename << endl;
#endif
          gSystem->Exec( cmd.str() ); // memory leak cmd.str()

          // 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 TSeqCollection* 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 TSeqCollection* 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 TSeqCollection* 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 TSeqCollection* TTabCom::GetListOfSysIncFiles()
{
     if( !fpSysIncFiles )
     {
          fpSysIncFiles = NewListOfFilesInPath( GetSysIncludePath() );
     }

     return fpSysIncFiles;

}
 const TSeqCollection* 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 TSeqCollection* 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[], TSeqCollection* 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);

     strstream   cmd;
     const char* tmpfile = tmpnam(0);
     cmd << "gROOT->ProcessLine("" << varName << ""); > " << tmpfile << endl;
     gROOT->ProcessLineSync(cmd.str()); // memory leak cmd.str()
     // 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 != "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
     {
          istrstream endings((char*)fignore); // do i need to make a copy first?
          TString    ending;

          ending.ReadToDelim( endings, ':' );

          while( !ending.IsNull() ) {
               if( s.EndsWith(ending) )
                    return kTRUE;
               else
                    ending.ReadToDelim( endings, ':' ); // 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);
     strstream cmd;
     cmd << "gROOT->ProcessLine(".include"); > " << tmpfilename << endl;
     gROOT->ProcessLineSync( cmd.str() ); // memory leak cmd.str()

     // 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, 0, &flags, 0 );
     return (int)flags & 2;
}
 TSeqCollection* 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)
     istrstream  path((char*)path1);
     TString     dirName;

     dirName.ReadToDelim( path, ':' );

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

          AppendListOfFilesInDirectory( dirName, pList );

          // next
          dirName.ReadToDelim( path, ':' );
     }

     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 TSeqCollection* 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, '/');
     strstream str;
     TString   dir;
     TString   newPath;
     str << originalPath;

#ifndef WIN32
     while( str )
#else
     while( 1 )
#endif
     {
          dir = "";
          dir.ReadToDelim( str, ':' );
          if( dir.IsNull() ) continue; // ignore blank entries
          newPath.Append( dir );
          if( !newPath.EndsWith("/") ) newPath.Append("/");
          newPath.Append( newBase );
          newPath.Append( ':' );
     }

     return newPath.Strip( TString::kTrailing, ':' );
}
 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 TSeqCollection* pListOfUsers = GetListOfUsers();

               pos = Complete( "[^~]*$", pListOfUsers, "/" );
          }
          break;
     case kSYS_EnvVar:
          {
               const TSeqCollection* 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 TSeqCollection* pListOfFiles = GetListOfFilesInPath( filePath.Data() );

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

     case kCINT_Exec:
     case kCINT_Load:
          {
               const TString   fileName     = s3("[^ ]*$");
               const TString   macroPath    = DeterminePath( fileName, TROOT::GetMacroPath() );
               const TSeqCollection*  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 TSeqCollection* 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 TSeqCollection* 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 TSeqCollection* pListOfFiles = GetListOfFilesInPath( filePath.Data() );

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

     case kCXX_ScopeMember:
     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_ScopeMember:    pClass = MakeClassFromClassName( name );        break;
               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( (TCollection*) pClass->GetListOfAllPublicMethods() );
               pList->AddAll( (TCollection*) pClass->GetListOfAllPublicDataMembers() );

               switch( context )
               {
               case kCXX_ScopeMember:    pos = Complete( "[^: ]*$", pList, "(" ); break;
               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:
     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( (TCollection*) 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;

               // shouldn't TCollection::AddAll( TCollection* ) take a const argument?
               const TSeqCollection* pL2 = GetListOfClasses();
               pList->AddAll(  (TSeqCollection*) pL2 );
               //
               const TSeqCollection* pC1 = GetListOfGlobals();
               pList->AddAll(  (TSeqCollection*) pC1 );
               //
               const TSeqCollection* pC3 = GetListOfGlobalFunctions();
               pList->AddAll(  (TSeqCollection*) 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_Load,  "^ *\.L .*$" );
     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, 0 );
     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::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, 0 );
}
 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 );
}


ROOT page - Class index - Top of the page

This page has been automatically generated. If you have any comments or suggestions about the page layout send a mail to ROOT support, or contact the developers with any questions or problems regarding ROOT.