/* * Copyright 1995, 2000 Perforce Software. All rights reserved. * * This file is part of Perforce - the FAST SCM System. */ /* * ClientUser -- default implementation of user interface for p4 client * * This file implements the p4 client command line interface by defining * the methods of the ClientUser class. This interface reads from stdin, * writes to stdout and stderr, and invokes the user's editor. */ # define NEED_FILE # ifdef OS_NT # include <process.h> # define NEED_FCNTL # endif # include "strbuf.h" # include "strops.h" # include "strdict.h" # include "enviro.h" # include "error.h" # include "signaler.h" # include "filesys.h" # include "diff.h" # include "clientuser.h" # include "clienterror.h" # ifdef OS_MAC # include <strarray.h> # include "clientusermac.h" # endif /* * NoEcho - turn on/off echoing */ // These next few lines will get ported to hell # ifdef OS_NT # define WIN32_LEAN_AND_MEAN # include <windows.h> typedef DWORD TIO_T; # define hstdin (HANDLE)_get_osfhandle( fileno( stdin ) ) # define TIO_GET(tio) GetConsoleMode( hstdin, &tio ) # define TIO_SET(tio) SetConsoleMode( hstdin, tio ) # define TIO_NOECHO(tio) tio &= ~ENABLE_ECHO_INPUT # endif # if defined( OS_OS2 ) || defined( OS_MAC ) || \ defined( OS_VMS ) || defined( OS_AS400 ) || \ defined( OS_MPEIX ) // no ops typedef int TIO_T; # define hstdin fileno( stdin ) # define TIO_GET(tio) {} # define TIO_SET(tio) {} # define TIO_NOECHO(tio) {} # endif // For now, default to termios # ifndef TIO_SET # define USE_TERMIOS # endif # ifdef USE_TERMIOS # include <termios.h> typedef struct termios TIO_T; # define TIO_GET(tio) tcgetattr( fileno( stdin ), &tio ) # define TIO_SET(tio) tcsetattr( fileno( stdin ), TCSANOW, &tio ) # define TIO_NOECHO(tio) tio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL) # endif # ifdef USE_SGTTY # include <sgtty.h> typedef struct sgttyb TIO_T; # define TIO_GET(tio) ioctl( fileno( stdin ), TIOCGETP, &tio ) # define TIO_SET(tio) ioctl( fileno( stdin ), TIOCSETP, &tio ) # define TIO_NOECHO(tio) tio.sg_flags &= ~(ECHO) # endif class NoEcho; void EchoCleanup( NoEcho *noEcho ); class NoEcho { public: NoEcho() { TIO_GET( tio ); tio2 = tio; TIO_NOECHO( tio ); TIO_SET( tio ); signaler.OnIntr( (SignalFunc)EchoCleanup, this ); } ~NoEcho() { TIO_SET( tio2 ); fputc( '\n', stdout ); signaler.DeleteOnIntr( this ); } private: TIO_T tio, tio2; } ; void EchoCleanup( NoEcho *noEcho ) { delete noEcho; } /* * ClientUser::Prompt() - prompt the user, and wait for a response. */ void ClientUser::Prompt( const StrPtr &msg, StrBuf &buf, int noEcho, Error *e ) { printf( msg.Text() ); # ifdef OS_MAC fputc( '\n', stdout ); # endif fflush( stdout ); fflush( stdin ); // Turn off echoing NoEcho *setEcho = noEcho ? new NoEcho : 0; // Prompt'em buf.Clear(); char *b = buf.Alloc( 1024 ); if( !fgets( b, 1024, stdin ) ) { e->Set( ErrClient::Eof ); buf.SetEnd( b ); } else { // Set end and truncate any newline buf.SetEnd( b += strlen( b ) ); if( buf.Length() && buf.End()[ -1 ] == '\n' ) { buf.SetEnd( buf.End() - 1 ); buf.AppendNull(); } } delete setEcho; } void ClientUser::Help( const_char *const *help ) { for( ; *help; help++ ) printf( "%s\n", *help ); } void ClientUser::HandleError( Error *err ) { // Dumb implementation is just to format // the error and call (old) OutputError. // API callers against 99.1+ servers can do better by // having HandleError probe 'err' directly for detail. StrBuf msg; err->Fmt( &msg ); OutputError( msg.Text() ); } void ClientUser::OutputError( const_char *errBuf ) { fwrite( errBuf, 1, strlen( errBuf ), stderr ); } void ClientUser::OutputInfo( char level, const_char *data ) { switch( level ) { default: case '0': break; case '1': printf( "... " ); break; case '2': printf( "... ... " ); break; } fwrite( data, 1, strlen( data ), stdout ); fputc( '\n', stdout ); } void ClientUser::OutputText( const_char *data, int length ) { fwrite( data, 1, length, stdout ); } void ClientUser::OutputBinary( const_char *data, int length ) { # ifdef OS_NT // we rely on a trailing zero length buffer to // tell us to turn off binary output. if( binaryStdout == !length ) { // toggle binaryStdout = !!length; fflush( stdout ); setmode( fileno( stdout ), binaryStdout ? O_BINARY : O_TEXT ); } # endif fwrite( data, 1, length, stdout ); } void ClientUser::InputData( StrBuf *buf, Error *e ) { int n; buf->Clear(); do { # ifdef OS_MAC // CW PRO 5 fread/read from stdin hangs machine char *b = buf->Alloc( FileBufferSize + 1 ); n = fgets( b, FileBufferSize + 1, stdin ) ? strlen( b ) : 0; # else char *b = buf->Alloc( FileBufferSize ); n = read( 0, b, FileBufferSize); # endif buf->SetEnd( b + n ); } while( n > 0 ); buf->AppendNull(); } void ClientUser::OutputStat( StrDict *varList ) { int i; StrBuf msg; StrPtr var, val; // Dump out the variables, using the GetVar( x ) interface. // Don't display the function (duh), which is only relevant to rpc. for( i = 0; varList->GetVar( i, var, val ); i++ ) { if( var == "func" ) continue; // otherAction and otherOpen go at level 2, as per 99.1 + earlier msg.Clear(); msg << var << " " << val; char level = strncmp( var.Text(), "other", 5 ) ? '1' : '2'; OutputInfo( level, msg.Text() ); } // blank line OutputInfo( '0', "" ); } void ClientUser::ErrorPause( char *errBuf, Error *e ) { StrBuf buf; OutputError( errBuf ); # if defined( OS_MAC ) Prompt( StrRef( "Hit enter to continue..." ), buf, 0, e ); # else Prompt( StrRef( "Hit return to continue..." ), buf, 0, e ); # endif } void ClientUser::Edit( FileSys *f1, Error *e ) { Edit( f1, enviro, e ); } void ClientUser::Edit( FileSys *f1, Enviro * env, Error *e ) { // The presence of $SHELL on NT implies MKS, which has vi. // On VMS, we look for POSIX$SHELL - if set, we'll use vi. // Otherwise, EDIT. if( !f1->IsTextual() ) { e->Set( ErrClient::CantEdit ) << f1->Name(); return; } # ifdef OS_MAC EditWithExternalEditor( f1->Name(), env, e ); # else StrBuf n; const char *editor; if( !( editor = env->Get( "P4EDITOR" ) ) ) editor = getenv( "EDITOR" ); # ifdef OS_NT if( !editor && !getenv( "SHELL" ) ) editor = "notepad"; # endif # ifdef OS_VMS if( !editor && !getenv( "POSIX$SHELL" ) ) editor = "edit"; # endif # ifdef OS_AS400 if( !editor ) editor = "edtf"; # endif // Bill Joy rules, Emacs maggots! if( !editor ) editor = "vi"; RunCmd( editor, f1->Name(), 0, 0, 0, 0, e ); # endif /* OS_MAC */ } void ClientUser::Diff( FileSys *f1, FileSys *f2, int doPage, char *df, Error *e ) { if( !f1->IsTextual() || !f2->IsTextual() ) { if( f1->Compare( f2, e ) ) printf( "(... files differ ...)\n" ); return; } // Call diff to do text compare const char *diff; const char *pager; if( !( diff = enviro->Get( "P4DIFF" ) ) ) diff = getenv( "DIFF" ); if( !doPage ) pager = 0; else if( !( pager = enviro->Get( "P4PAGER" ) ) ) pager = getenv( "PAGER" ); // Do our own diff // VMS ReadFile is busted. # ifdef OS_VMS if( !diff ) diff = "diff"; # else /* not VMS */ if( !diff ) { if( pager ) { FileSys *t = FileSys::CreateGlobalTemp( f1->GetType() ); { # ifndef OS_NEXT :: # endif Diff d; d.SetInput( f1->Name(), f2->Name(), df, e ); if( !e->Test() ) d.SetOutput( t->Name(), e ); if( !e->Test() ) d.DiffWithFlags( df ); d.CloseOutput( e ); } if( !e->Test() ) RunCmd( pager, t->Name(), 0, 0, 0, 0, e ); delete t; } else { # ifndef OS_NEXT :: # endif Diff d; d.SetInput( f1->Name(), f2->Name(), df, e ); if( !e->Test() ) d.SetOutput( stdout ); if( !e->Test() ) d.DiffWithFlags( df ); d.CloseOutput( e ); } return; } # endif /* VMS */ // Build up flags args // Yuk. if( !df || !*df ) { RunCmd( diff, 0, f1->Name(), f2->Name(), 0, pager, e ); } else { StrBuf flags; flags.Set( "-", 1 ); flags << df; RunCmd( diff, flags.Text(), f1->Name(), f2->Name(), 0, pager, e ); } } void ClientUser::Merge( FileSys *base, FileSys *leg1, FileSys *leg2, FileSys *result, Error *e ) { char *merger; if( !( merger = enviro->Get( "P4MERGE" ) ) && !( merger = getenv( "MERGE" ) ) ) { e->Set( ErrClient::NoMerger ); return; } RunCmd( merger, base->Name(), leg1->Name(), leg2->Name(), result->Name(), 0, e ); } void ClientUser::RunCmd( const char *command, const char *arg1, const char *arg2, const char *arg3, const char *arg4, const char *pager, Error *e ) { StrBuf n; fflush( stdout ); signaler.Block(); // reset SIGINT to SIGDFL # ifdef OS_MAC n << command; if( arg1 ) n << " '" << arg1 << "'"; if( arg2 ) n << " '" << arg2 << "'"; if( arg3 ) n << " '" << arg3 << "'"; if( arg4 ) n << " '" << arg4 << "'"; ToolServerExec( n.Text(), e ); # else # ifdef OS_NT if( !pager ) { n << command; const char * argv[32]; int c = StrOps::Words( n, (char **)argv, 32 ); if( arg1 ) argv[ c++ ] = arg1; if( arg2 ) argv[ c++ ] = arg2; if( arg3 ) argv[ c++ ] = arg3; if( arg4 ) argv[ c++ ] = arg4; argv[ c++ ] = 0; if( spawnvp( _P_WAIT, argv[0], argv ) < 0 ) e->Sys( argv[0], arg1 ); signaler.Catch(); // catch SIGINT again return; } # endif /* OS_NT */ # ifdef OS_VMS # define QUOTE "" # else # ifdef OS_OS2 # define QUOTE "\"" # else # define QUOTE "'" # endif # endif n << command; if( arg1 ) n << " " QUOTE << arg1 << QUOTE; if( arg2 ) n << " " QUOTE << arg2 << QUOTE; if( arg3 ) n << " " QUOTE << arg3 << QUOTE; if( arg4 ) n << " " QUOTE << arg4 << QUOTE; if( pager ) n << " | " << pager; system( n.Text() ); # endif /* OS_MAC */ signaler.Catch(); // catch SIGINT again } FileSys * ClientUser::File( FileSysType type ) { return FileSys::Create( type ); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#2 | 937 | Sam Stafford |
Renaming my guest directory to the more conventional sam_stafford. |
||
#1 | 936 | Sam Stafford |
Adding P4HL to the public depot. See relnotes.txt for installation instructions; all relevant files are under p4hl/dist. Source code is under p4hl/src in the form of a VC++ project. |