diff --git a/code/client/cl_keys.cpp b/code/client/cl_keys.cpp index d1f7caf..e67aa80 100644 --- a/code/client/cl_keys.cpp +++ b/code/client/cl_keys.cpp @@ -758,6 +758,13 @@ int Key_GetKey( const char* binding ) } +void Key_KeyNameCompletion( void (*callback)(const char *s) ) +{ + for( int i = 0; keynames[i].name != NULL; i++ ) + callback( keynames[i].name ); +} + + // write bindings to a config file as "bind key value" so they can be exec'ed later void Key_WriteBindings( fileHandle_t f ) @@ -830,6 +837,22 @@ static void Key_Bind_f() } +static void Key_CompleteBind_f( int startArg, int compArg ) +{ + if ( compArg == startArg + 1 ) + Field_AutoCompleteKeyName( startArg, compArg ); + else if ( compArg >= startArg + 2 ) + Field_AutoCompleteFrom( compArg, compArg, qtrue, qtrue ); +} + + +static void Key_CompleteUnbind_f( int startArg, int compArg ) +{ + if ( compArg == startArg + 1 ) + Field_AutoCompleteKeyName( startArg, compArg ); +} + + static void Key_Bindlist_f() { for ( int i = 0; i < MAX_KEYS; ++i ) { @@ -845,7 +868,9 @@ void CL_InitKeyCommands() COMPILE_TIME_ASSERT( K_LAST_KEY <= MAX_KEYS ); Cmd_AddCommand( "bind", Key_Bind_f ); + Cmd_SetAutoCompletion( "bind", Key_CompleteBind_f ); Cmd_AddCommand( "unbind", Key_Unbind_f ); + Cmd_SetAutoCompletion( "unbind", Key_CompleteUnbind_f ); Cmd_AddCommand( "unbindall", Key_Unbindall_f ); Cmd_AddCommand( "bindlist", Key_Bindlist_f ); } diff --git a/code/client/cl_main.cpp b/code/client/cl_main.cpp index 7bb1604..9e38310 100644 --- a/code/client/cl_main.cpp +++ b/code/client/cl_main.cpp @@ -263,6 +263,13 @@ static void CL_Record_f() } +static void CL_CompleteDemoRecord_f( int startArg, int compArg ) +{ + if ( startArg + 1 == compArg ) + Field_AutoCompleteDemoNameWrite( startArg, compArg ); +} + + /* ======================================================================= @@ -425,6 +432,13 @@ void CL_PlayDemo_f() } +static void CL_CompleteDemoPlay_f( int startArg, int compArg ) +{ + if ( startArg + 1 == compArg ) + Field_AutoCompleteDemoNameRead( startArg, compArg ); +} + + /* ==================== CL_StartDemoLoop @@ -995,6 +1009,13 @@ static void CL_Rcon_f( void ) } +static void CL_CompleteRcon_f( int startArg, int compArg ) +{ + if ( startArg < compArg ) + Field_AutoCompleteFrom( startArg + 1, compArg, qtrue, qtrue ); +} + + // if we are pure we need to send back a command with our referenced pk3 checksums static void CL_SendPureChecksums() @@ -1947,6 +1968,19 @@ qbool CL_CDKeyValidate( const char *key, const char *checksum ) } +static void CL_CallVote_f() +{ + CL_ForwardCommandToServer( Cmd_Cmd() ); +} + + +static void CL_CompleteCallVote_f( int startArg, int compArg ) +{ + if ( compArg == startArg + 2 && !Q_stricmp( Cmd_Argv( startArg + 1 ), "map" ) ) + Field_AutoCompleteMapName( startArg, compArg ); +} + + void CL_Init() { //QSUBSYSTEM_INIT_START( "Client" ); @@ -2019,7 +2053,9 @@ void CL_Init() Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f); Cmd_AddCommand ("disconnect", CL_Disconnect_f); Cmd_AddCommand ("record", CL_Record_f); + Cmd_SetAutoCompletion ("record", CL_CompleteDemoRecord_f); Cmd_AddCommand ("demo", CL_PlayDemo_f); + Cmd_SetAutoCompletion ("demo", CL_CompleteDemoPlay_f); Cmd_AddCommand ("cinematic", CL_PlayCinematic_f); Cmd_AddCommand ("stoprecord", CL_StopRecord_f); Cmd_AddCommand ("connect", CL_Connect_f); @@ -2027,6 +2063,7 @@ void CL_Init() Cmd_AddCommand ("localservers", CL_LocalServers_f); Cmd_AddCommand ("globalservers", CL_GlobalServers_f); Cmd_AddCommand ("rcon", CL_Rcon_f); + Cmd_SetAutoCompletion ("rcon", CL_CompleteRcon_f); Cmd_AddCommand ("ping", CL_Ping_f ); Cmd_AddCommand ("serverstatus", CL_ServerStatus_f ); Cmd_AddCommand ("showip", CL_ShowIP_f ); @@ -2036,6 +2073,12 @@ void CL_Init() Cmd_AddCommand ("video", CL_Video_f ); Cmd_AddCommand ("stopvideo", CL_StopVideo_f ); + // we use these until we get proper handling on the mod side + Cmd_AddCommand ("cv", CL_CallVote_f ); + Cmd_AddCommand ("callvote", CL_CallVote_f ); + Cmd_SetAutoCompletion ("cv", CL_CompleteCallVote_f ); + Cmd_SetAutoCompletion ("callvote", CL_CompleteCallVote_f ); + CL_InitRef(); SCR_Init(); @@ -2093,6 +2136,10 @@ void CL_Shutdown() Cmd_RemoveCommand ("video"); Cmd_RemoveCommand ("stopvideo"); + // we use these until we get proper handling on the mod side + Cmd_RemoveCommand ("cv"); + Cmd_RemoveCommand ("callvote"); + Cvar_Set( "cl_running", "0" ); recursive = qfalse; diff --git a/code/qcommon/cmd.cpp b/code/qcommon/cmd.cpp index e3552fc..7258b5f 100644 --- a/code/qcommon/cmd.cpp +++ b/code/qcommon/cmd.cpp @@ -158,7 +158,7 @@ void Cbuf_Execute() // SCRIPT COMMANDS -void Cmd_Exec_f() +static void Cmd_Exec_f() { char *f; int len; @@ -184,9 +184,16 @@ void Cmd_Exec_f() } +static void Cmd_CompleteExec_f( int startArg, int compArg ) +{ + if ( startArg + 1 == compArg ) + Field_AutoCompleteConfigName( startArg, compArg ); +} + + // inserts the current value of a variable as command text -void Cmd_Vstr_f() +static void Cmd_Vstr_f() { if (Cmd_Argc() != 2) { Com_Printf( "vstr : execute a variable command\n" ); @@ -196,6 +203,13 @@ void Cmd_Vstr_f() } +static void Cmd_CompleteVstr_f( int startArg, int compArg ) +{ + if ( compArg == startArg + 1 ) + Field_AutoCompleteFrom( compArg, compArg, qfalse, qtrue ); +} + + // just prints the rest of the line to the console void Cmd_Echo_f() @@ -215,11 +229,14 @@ typedef struct cmd_function_s struct cmd_function_s *next; char *name; xcommand_t function; + xcommandCompletion_t completion; } cmd_function_t; static int cmd_argc; static char* cmd_argv[MAX_STRING_TOKENS]; // points into cmd_tokenized +static int cmd_argoffsets[MAX_STRING_TOKENS]; // offsets into cmd_cmd +static qbool cmd_quoted[MAX_STRING_TOKENS]; // set to 1 if the input was quoted static char cmd_tokenized[BIG_INFO_STRING+MAX_STRING_TOKENS]; // will have 0 bytes inserted static char cmd_cmd[BIG_INFO_STRING]; // the original command we received (no token processing) @@ -261,6 +278,28 @@ const char* Cmd_ArgsFrom( int arg ) } +qbool Cmd_ArgQuoted( int arg ) +{ + if (arg < 0 || arg >= cmd_argc) + return qfalse; + + return cmd_quoted[arg]; +} + + +int Cmd_ArgIndexFromOffset( int offset ) +{ + for (int i = 0; i < cmd_argc; ++i) { + const int start = cmd_argoffsets[i]; + const int end = start + strlen( cmd_argv[i] ); + if (offset >= start && offset <= end) + return i; + } + + return -1; +} + + const char* Cmd_Args() { return Cmd_ArgsFrom(1); @@ -305,6 +344,7 @@ static void Cmd_TokenizeString2( const char* text, qbool ignoreQuotes ) Q_strncpyz( cmd_cmd, text, sizeof(cmd_cmd) ); char* out = cmd_tokenized; + const char* const textStart = text; while ( 1 ) { if ( cmd_argc == MAX_STRING_TOKENS ) { @@ -342,6 +382,8 @@ static void Cmd_TokenizeString2( const char* text, qbool ignoreQuotes ) // handle quoted strings - NOTE: this doesn't handle \" escaping if ( !ignoreQuotes && *text == '"' ) { cmd_argv[cmd_argc] = out; + cmd_argoffsets[cmd_argc] = text + 1 - textStart; // jump past the opening quote + cmd_quoted[cmd_argc] = qtrue; cmd_argc++; text++; while ( *text && *text != '"' ) { @@ -357,6 +399,8 @@ static void Cmd_TokenizeString2( const char* text, qbool ignoreQuotes ) // regular token cmd_argv[cmd_argc] = out; + cmd_argoffsets[cmd_argc] = text - textStart; + cmd_quoted[cmd_argc] = qfalse; cmd_argc++; // skip until whitespace, quote, or command @@ -396,7 +440,7 @@ void Cmd_TokenizeStringIgnoreQuotes( const char* text ) } -void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) +void Cmd_AddCommand( const char* cmd_name, xcommand_t function ) { cmd_function_t* cmd; @@ -415,12 +459,13 @@ void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) cmd = (cmd_function_t*)S_Malloc(sizeof(cmd_function_t)); cmd->name = CopyString( cmd_name ); cmd->function = function; + cmd->completion = NULL; cmd->next = cmd_functions; cmd_functions = cmd; } -void Cmd_RemoveCommand( const char *cmd_name ) +void Cmd_RemoveCommand( const char* cmd_name ) { cmd_function_t** back = &cmd_functions; @@ -443,7 +488,32 @@ void Cmd_RemoveCommand( const char *cmd_name ) } -void Cmd_CommandCompletion( void(*callback)(const char *s) ) +void Cmd_SetAutoCompletion( const char* cmd_name, xcommandCompletion_t completion ) +{ + cmd_function_t* cmd; + for ( cmd = cmd_functions; cmd; cmd = cmd->next ) { + if ( !strcmp( cmd_name, cmd->name ) ) { + cmd->completion = completion; + return; + } + } +} + + +void Cmd_AutoCompleteArgument( const char* cmd_name, int startArg, int compArg ) +{ + const cmd_function_t* cmd; + for ( cmd = cmd_functions; cmd; cmd = cmd->next ) { + if ( !strcmp( cmd_name, cmd->name ) ) { + if ( cmd->completion ) + cmd->completion( startArg, compArg ); + return; + } + } +} + + +void Cmd_CommandCompletion( void(*callback)(const char* s) ) { const cmd_function_t* cmd; for ( cmd = cmd_functions; cmd; cmd = cmd->next ) { @@ -533,7 +603,9 @@ void Cmd_Init() { Cmd_AddCommand( "cmdlist", Cmd_List_f ); Cmd_AddCommand( "exec", Cmd_Exec_f ); + Cmd_SetAutoCompletion( "exec", Cmd_CompleteExec_f ); Cmd_AddCommand( "vstr", Cmd_Vstr_f ); + Cmd_SetAutoCompletion( "vstr", Cmd_CompleteVstr_f ); Cmd_AddCommand( "echo", Cmd_Echo_f ); Cmd_AddCommand( "wait", Cmd_Wait_f ); } diff --git a/code/qcommon/common.cpp b/code/qcommon/common.cpp index 1c46eb5..adb46c0 100644 --- a/code/qcommon/common.cpp +++ b/code/qcommon/common.cpp @@ -98,6 +98,7 @@ qbool com_fullyInitialized; static char com_errorMessage[MAXPRINTMSG]; static void Com_WriteConfig_f(); +static void Com_CompleteWriteConfig_f( int startArg, int compArg ); extern void CIN_CloseAllVideos( void ); //============================================================================ @@ -2219,6 +2220,7 @@ void Com_Init( char *commandLine ) Cmd_AddCommand( "quit", Com_Quit_f ); Cmd_AddCommand( "writeconfig", Com_WriteConfig_f ); + Cmd_SetAutoCompletion( "writeconfig", Com_CompleteWriteConfig_f ); const char* s = Q3_VERSION" "PLATFORM_STRING" "__DATE__; com_version = Cvar_Get( "version", s, CVAR_ROM | CVAR_SERVERINFO ); @@ -2311,6 +2313,13 @@ static void Com_WriteConfig_f() } +static void Com_CompleteWriteConfig_f( int startArg, int compArg ) +{ + if ( startArg + 1 == compArg ) + Field_AutoCompleteConfigName( startArg, compArg ); +} + + static int Com_ModifyMsec( int msec ) { // modify time for debugging values @@ -2542,6 +2551,51 @@ float Q_acos(float c) } +/* +================== +crc32 routines +================== +*/ + +static unsigned int crc32_table[256]; +static qboolean crc32_inited = qfalse; + +void crc32_init( unsigned int *crc ) +{ + unsigned int c; + int i, j; + + if ( !crc32_inited ) + { + for (i = 0; i < 256; i++) + { + c = i; + for ( j = 0; j < 8; j++ ) + c = c & 1 ? (c >> 1) ^ 0xEDB88320UL : c >> 1; + crc32_table[i] = c; + } + crc32_inited = qtrue; + } + + *crc = 0xFFFFFFFFUL; +} + + +void crc32_update( unsigned int *crc, unsigned char *buf, unsigned int len ) +{ + while ( len-- ) + { + *crc = crc32_table[(*crc ^ *buf++) & 0xFF] ^ (*crc >> 8); + } +} + + +void crc32_final( unsigned int *crc ) +{ + *crc = *crc ^ 0xFFFFFFFFUL; +} + + /* =========================================== command line completion @@ -2605,459 +2659,223 @@ static void PrintCvarMatches( const char *s ) } -/* -================== -crc32 routines -================== -*/ - -static unsigned int crc32_table[256]; -static qboolean crc32_inited = qfalse; - -void crc32_init( unsigned int *crc ) +static void Field_AppendArgString( field_t *field, int arg, const char *s ) { - unsigned int c; - int i, j; - - if ( !crc32_inited ) - { - for (i = 0; i < 256; i++) - { - c = i; - for ( j = 0; j < 8; j++ ) - c = c & 1 ? (c >> 1) ^ 0xEDB88320UL : c >> 1; - crc32_table[i] = c; - } - crc32_inited = qtrue; - } - - *crc = 0xFFFFFFFFUL; + const qbool quoted = Cmd_ArgQuoted( arg ); + if ( quoted ) + Q_strcat( field->buffer, sizeof( field->buffer ), "\"" ); + Q_strcat( field->buffer, sizeof( field->buffer ), s ); + if ( quoted ) + Q_strcat( field->buffer, sizeof( field->buffer ), "\"" ); } -void crc32_update( unsigned int *crc, unsigned char *buf, unsigned int len ) +static void Field_AppendArg( field_t *field, int arg ) { - while ( len-- ) - { - *crc = crc32_table[(*crc ^ *buf++) & 0xFF] ^ (*crc >> 8); + Field_AppendArgString( field, arg, Cmd_Argv( arg ) ); +} + + +static void Field_AppendLastArgs( field_t *field, int startArg ) +{ + const int argc = Cmd_Argc(); + for ( int i = startArg; i < argc; ++i ) { + Q_strcat( field->buffer, sizeof( field->buffer ), " " ); + Field_AppendArg( field, i ); } } -void crc32_final( unsigned int *crc ) +static void Field_AppendFirstArgs( field_t *field, int count ) { - *crc = *crc ^ 0xFFFFFFFFUL; -} + if ( count <= 0 ) + return; - -#if I_EVER_NAG_TIMBO_INTO_FIXING_THIS - - -/* -================== -Com_CharIsOneOfCharset -================== -*/ -static qbool Com_CharIsOneOfCharset( char c, char *set ) -{ - int i; - - for( i = 0; i < strlen( set ); i++ ) - { - if( set[ i ] == c ) - return qtrue; + Field_AppendArg( field, 0 ); + for ( int i = 1; i < count; ++i ) { + Q_strcat( field->buffer, sizeof( field->buffer ), " " ); + Field_AppendArg( field, i ); } - - return qfalse; } -/* -================== -Com_SkipCharset -================== -*/ -char *Com_SkipCharset( char *s, char *sep ) + +static qbool String_HasLeadingSlash( const char *arg ) { - char *p = s; - - while( p ) - { - if( Com_CharIsOneOfCharset( *p, sep ) ) - p++; - else - break; - } - - return p; + return ((*arg == '\\') || (*arg == '/')); } -/* -================== -Com_SkipTokens -================== -*/ -char *Com_SkipTokens( char *s, int numTokens, char *sep ) -{ - int sepCount = 0; - char *p = s; - while( sepCount < numTokens ) - { - if( Com_CharIsOneOfCharset( *p++, sep ) ) - { - sepCount++; - while( Com_CharIsOneOfCharset( *p, sep ) ) - p++; - } - else if( *p == '\0' ) - break; - } +// returns qtrue if the match list should be printed +static qboolean Field_CompleteShortestMatch( int startArg, int compArg ) +{ + if ( matchCount == 0 ) + return qfalse; + + field_t *const field = completionField; + + // write the first part of the field + *field->buffer = '\0'; + if ( compArg > 0 ) { + Field_AppendFirstArgs( field, compArg ); + Q_strcat( field->buffer, sizeof( field->buffer ), " " ); + } + Field_AppendArgString( field, compArg, shortestMatch ); + + if ( matchCount == 1 ) { + // finish the field with the only match + if ( compArg == Cmd_Argc() - 1 ) + Q_strcat( field->buffer, sizeof( field->buffer ), " " ); + field->cursor = strlen( field->buffer ); + Field_AppendLastArgs( field, compArg + 1 ); + return qfalse; + } - if( sepCount == numTokens ) - return p; - else - return s; + // finish the field with the shortest match and echo the command + field->cursor = strlen( field->buffer ); + if ( Cmd_ArgQuoted( compArg ) ) + field->cursor--; + Field_AppendLastArgs( field, compArg + 1 ); + Com_Printf( "]%s\n", Cmd_Cmd() ); + + return qtrue; } -/* -=============== -Field_FindFirstSeparator -=============== -*/ -static char *Field_FindFirstSeparator( char *s ) + +static void Field_AutoCompleteCmdOrVarName( int startArg, int compArg, qbool searchCmds, qbool searchVars ) { - int i; + if ( !searchCmds && !searchVars ) + return; - for( i = 0; i < strlen( s ); i++ ) - { - if( s[ i ] == ';' ) - return &s[ i ]; - } + if ( searchCmds ) + Cmd_CommandCompletion( FindMatches ); + if ( searchVars ) + Cvar_CommandCompletion( FindMatches ); - return NULL; + if ( !Field_CompleteShortestMatch( startArg, compArg ) ) + return; + + if ( searchCmds ) + Cmd_CommandCompletion( PrintMatches ); + if ( searchVars ) + Cvar_CommandCompletion( PrintCvarMatches ); } -/* -=============== -Field_CompleteFilename -=============== -*/ -static void Field_CompleteFilename( const char *dir, - const char *ext, qbool stripExt ) + +static void Field_AutoCompleteCommandArgument( int startArg, int compArg ) { + const char* cmdName = Cmd_Argv( startArg ); + if ( String_HasLeadingSlash( cmdName ) ) + cmdName++; + if ( *cmdName == '\0' ) + return; + + Cmd_AutoCompleteArgument( cmdName, startArg, compArg ); +} + + +void Field_AutoCompleteFrom( int startArg, int compArg, qbool searchCmds, qbool searchVars ) +{ + // clear results matchCount = 0; - shortestMatch[ 0 ] = 0; + *shortestMatch = '\0'; - FS_FilenameCompletion( dir, ext, stripExt, FindMatches ); - - if( matchCount == 0 ) - return; - - Q_strcat( completionField->buffer, sizeof( completionField->buffer ), - shortestMatch + strlen( completionString ) ); - completionField->cursor = strlen( completionField->buffer ); - - if( matchCount == 1 ) - { - Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); - completionField->cursor++; - return; - } - - Com_Printf( "]%s\n", completionField->buffer ); - - FS_FilenameCompletion( dir, ext, stripExt, PrintMatches ); + // For the first argument, we always check both variables and commands. + // For other arguments, we run a custom auto-completion handler + // registered by the command if one was provided. + if ( compArg == startArg ) + Field_AutoCompleteCmdOrVarName( startArg, compArg, searchCmds, searchVars ); + else + Field_AutoCompleteCommandArgument( startArg, compArg ); } -/* -=============== -Field_CompleteCommand -=============== -*/ -static void Field_CompleteCommand( char *cmd, - qbool doCommands, qbool doCvars ) -{ - int completionArgument = 0; - char *p; - // Skip leading whitespace and quotes - cmd = Com_SkipCharset( cmd, " \"" ); - - Cmd_TokenizeStringIgnoreQuotes( cmd ); - completionArgument = Cmd_Argc( ); - - // If there is trailing whitespace on the cmd - if( *( cmd + strlen( cmd ) - 1 ) == ' ' ) - { - completionString = ""; - completionArgument++; - } - else - completionString = Cmd_Argv( completionArgument - 1 ); - - if( completionArgument > 1 ) - { - const char *baseCmd = Cmd_Argv( 0 ); - -#ifndef DEDICATED - // If the very first token does not have a leading \ or /, - // refuse to autocomplete - if( cmd == completionField->buffer ) - { - if( baseCmd[ 0 ] != '\\' && baseCmd[ 0 ] != '/' ) - return; - baseCmd++; - } -#endif - - if( ( p = Field_FindFirstSeparator( cmd ) ) ) - { - // Compound command - Field_CompleteCommand( p + 1, qtrue, qtrue ); - } - else - { - // FIXME: all this junk should really be associated with the respective - // commands, instead of being hard coded here - if( ( !Q_stricmp( baseCmd, "map" ) || - !Q_stricmp( baseCmd, "devmap" ) || - !Q_stricmp( baseCmd, "spmap" ) || - !Q_stricmp( baseCmd, "spdevmap" ) ) && - completionArgument == 2 ) - { - Field_CompleteFilename( "maps", "bsp", qtrue ); - } - else if( ( !Q_stricmp( baseCmd, "exec" ) || - !Q_stricmp( baseCmd, "writeconfig" ) ) && - completionArgument == 2 ) - { - Field_CompleteFilename( "", "cfg", qfalse ); - } - else if( !Q_stricmp( baseCmd, "condump" ) && - completionArgument == 2 ) - { - Field_CompleteFilename( "", "txt", qfalse ); - } - else if( !Q_stricmp( baseCmd, "demo" ) && completionArgument == 2 ) - { - char demoExt[ 16 ]; - - Com_sprintf( demoExt, sizeof( demoExt ), ".dm_%d", PROTOCOL_VERSION ); - Field_CompleteFilename( "demos", demoExt, qtrue ); - } - else if( ( !Q_stricmp( baseCmd, "toggle" ) || - !Q_stricmp( baseCmd, "vstr" ) || - !Q_stricmp( baseCmd, "set" ) || - !Q_stricmp( baseCmd, "seta" ) || - !Q_stricmp( baseCmd, "setu" ) || - !Q_stricmp( baseCmd, "sets" ) ) && - completionArgument == 2 ) - { - // Skip " " - p = Com_SkipTokens( cmd, 1, " " ); - - if( p > cmd ) - Field_CompleteCommand( p, qfalse, qtrue ); - } - else if( !Q_stricmp( baseCmd, "rcon" ) && completionArgument == 2 ) - { - // Skip "rcon " - p = Com_SkipTokens( cmd, 1, " " ); - - if( p > cmd ) - Field_CompleteCommand( p, qtrue, qtrue ); - } - else if( !Q_stricmp( baseCmd, "bind" ) && completionArgument >= 3 ) - { - // Skip "bind " - p = Com_SkipTokens( cmd, 2, " " ); - - if( p > cmd ) - Field_CompleteCommand( p, qtrue, qtrue ); - } - } - } - else - { - if( completionString[0] == '\\' || completionString[0] == '/' ) - completionString++; - - matchCount = 0; - shortestMatch[ 0 ] = 0; - - if( strlen( completionString ) == 0 ) - return; - - if( doCommands ) - Cmd_CommandCompletion( FindMatches ); - - if( doCvars ) - Cvar_CommandCompletion( FindMatches ); - - if( matchCount == 0 ) - return; // no matches - - if( cmd == completionField->buffer ) - { -#ifndef DEDICATED - Com_sprintf( completionField->buffer, - sizeof( completionField->buffer ), "\\%s", shortestMatch ); -#else - Com_sprintf( completionField->buffer, - sizeof( completionField->buffer ), "%s", shortestMatch ); -#endif - } - else - { - Q_strcat( completionField->buffer, sizeof( completionField->buffer ), - shortestMatch + strlen( completionString ) ); - } - - completionField->cursor = strlen( completionField->buffer ); - - if( matchCount == 1 ) - { - Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); - completionField->cursor++; - return; - } - - Com_Printf( "]%s\n", completionField->buffer ); - - // run through again, printing matches - if( doCommands ) - Cmd_CommandCompletion( PrintMatches ); - - if( doCvars ) - Cvar_CommandCompletion( PrintCvarMatches ); - } -} - -/* -=============== -Field_AutoComplete - -Perform Tab expansion -=============== -*/ void Field_AutoComplete( field_t *field ) { completionField = field; - Field_CompleteCommand( completionField->buffer, qtrue, qtrue ); -} - - -#else -// use the id tab-completion code, which doesn't have all the nice stuff timbo did -// but has a "familiar" set of bugs rather than a new and thus even-more-hated set - -static void keyConcatArgs() -{ - int i; - const char* arg; - - for ( i = 1 ; i < Cmd_Argc() ; i++ ) { - Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); - arg = Cmd_Argv( i ); - while (*arg) { - if (*arg == ' ') { - Q_strcat( completionField->buffer, sizeof( completionField->buffer ), "\""); - break; - } - arg++; - } - Q_strcat( completionField->buffer, sizeof( completionField->buffer ), Cmd_Argv( i ) ); - if (*arg == ' ') { - Q_strcat( completionField->buffer, sizeof( completionField->buffer ), "\""); - } - } -} - -static void ConcatRemaining( const char *src, const char *start ) -{ - const char* str = strstr( src, start ); - - if (!str) { - keyConcatArgs(); + // first, decide which argument we're going to run completion on + Cmd_TokenizeString( field->buffer ); + const int compArg = Cmd_Argc() == 1 ? 0 : Cmd_ArgIndexFromOffset( field->cursor ); + if ( compArg < 0 || compArg >= Cmd_Argc() ) return; - } - str += strlen( start ); - Q_strcat( completionField->buffer, sizeof( completionField->buffer ), str ); -} - -/* -perform Tab expansion -NOTE TTimo this was originally client code only - moved to common code when writing tty console for *nix dedicated server -*/ -static void Field_CompleteCommand( field_t *field ) -{ - completionField = field; - - // only look at the first token for completion purposes - Cmd_TokenizeString( completionField->buffer ); - - completionString = Cmd_Argv(0); + // now select the actual string that needs completing + completionString = Cmd_Argv( compArg ); #ifndef DEDICATED - if ( completionString[0] == '\\' || completionString[0] == '/' ) { + if ( compArg == 0 && String_HasLeadingSlash( completionString ) ) completionString++; - } #endif - matchCount = 0; - shortestMatch[0] = 0; - - if ( *completionString == '\0' ) { + if ( *completionString == '\0' ) return; - } - Cmd_CommandCompletion( FindMatches ); - Cvar_CommandCompletion( FindMatches ); + Field_AutoCompleteFrom( 0, compArg, qtrue, qtrue ); + // get rid of any superfluous space between arguments if ( matchCount == 0 ) { - return; // no matches + *field->buffer = '\0'; + Field_AppendFirstArgs( field, Cmd_Argc() ); + field->cursor = strlen( field->buffer ); } - field_t temp; - Com_Memcpy( &temp, completionField, sizeof(field_t) ); - - if ( matchCount == 1 ) { - Com_sprintf( completionField->buffer, sizeof( completionField->buffer ), -#ifndef DEDICATED - "\\%s", shortestMatch ); -#else - "%s", shortestMatch ); -#endif - if ( Cmd_Argc() == 1 ) { - Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); - } else { - ConcatRemaining( temp.buffer, completionString ); + // make sure we have a leading backslash + if ( !String_HasLeadingSlash( field->buffer ) ) { + const size_t length = strlen( field->buffer ); + if ( length + 1 < sizeof( field->buffer ) ) { + memmove( field->buffer + 1, field->buffer, length + 1 ); + *field->buffer = '\\'; + field->cursor++; } - completionField->cursor = strlen( completionField->buffer ); - return; } - - // multiple matches, complete to shortest - Com_sprintf( completionField->buffer, sizeof( completionField->buffer ), -#ifndef DEDICATED - "\\%s", shortestMatch ); -#else - "%s", shortestMatch ); -#endif - completionField->cursor = strlen( completionField->buffer ); - ConcatRemaining( temp.buffer, completionString ); - - Com_Printf( "]%s\n", completionField->buffer ); - - // run through again, printing matches - Cmd_CommandCompletion( PrintMatches ); - Cvar_CommandCompletion( PrintCvarMatches ); } -void Field_AutoComplete( field_t *field ) + +void Field_AutoCompleteMapName( int startArg, int compArg ) { - Field_CompleteCommand( field ); + FS_FilenameCompletion( "maps", "bsp", qtrue, FindMatches, 0 ); + if ( Field_CompleteShortestMatch( startArg, compArg ) ) + FS_FilenameCompletion( "maps", "bsp", qtrue, PrintMatches, 0 ); } -#endif + +void Field_AutoCompleteConfigName( int startArg, int compArg ) +{ + FS_FilenameCompletion( "", "cfg", qtrue, FindMatches, FS_FILTER_INPAK ); + if ( Field_CompleteShortestMatch( startArg, compArg ) ) + FS_FilenameCompletion( "", "cfg", qtrue, PrintMatches, FS_FILTER_INPAK ); +} + + +#define DEMO_EXT "dm_"STRINGIZE(PROTOCOL_VERSION) + + +void Field_AutoCompleteDemoNameRead( int startArg, int compArg ) +{ + FS_FilenameCompletion( "demos", "dm_66", qtrue, FindMatches, 0 ); + FS_FilenameCompletion( "demos", "dm_67", qtrue, FindMatches, 0 ); + FS_FilenameCompletion( "demos", DEMO_EXT, qtrue, FindMatches, 0 ); + if ( Field_CompleteShortestMatch( startArg, compArg ) ) + { + FS_FilenameCompletion( "demos", "dm_66", qtrue, PrintMatches, 0 ); + FS_FilenameCompletion( "demos", "dm_67", qtrue, PrintMatches, 0 ); + FS_FilenameCompletion( "demos", DEMO_EXT, qtrue, PrintMatches, 0 ); + } +} + + +void Field_AutoCompleteDemoNameWrite( int startArg, int compArg ) +{ + FS_FilenameCompletion( "demos", DEMO_EXT, qtrue, FindMatches, FS_FILTER_INPAK ); + if ( Field_CompleteShortestMatch( startArg, compArg ) ) + FS_FilenameCompletion( "demos", DEMO_EXT, qtrue, PrintMatches, FS_FILTER_INPAK ); +} + + +#undef DEMO_EXT + + +void Field_AutoCompleteKeyName( int startArg, int compArg ) +{ + Key_KeyNameCompletion( FindMatches ); + if ( Field_CompleteShortestMatch( startArg, compArg ) ) + Key_KeyNameCompletion( PrintMatches ); +} diff --git a/code/qcommon/cvar.cpp b/code/qcommon/cvar.cpp index 30fb15a..c3e3b66 100644 --- a/code/qcommon/cvar.cpp +++ b/code/qcommon/cvar.cpp @@ -398,6 +398,13 @@ qbool Cvar_Command() } +static void Cvar_CompleteName( int startArg, int compArg ) +{ + if ( startArg + 1 == compArg ) + Field_AutoCompleteFrom( compArg, compArg, qfalse, qtrue ); +} + + // toggles a cvar for easy single key binding static void Cvar_Toggle_f( void ) @@ -676,4 +683,10 @@ void Cvar_Init() Cmd_AddCommand( "reset", Cvar_Reset_f ); Cmd_AddCommand( "cvarlist", Cvar_List_f ); Cmd_AddCommand( "cvar_restart", Cvar_Restart_f ); + Cmd_SetAutoCompletion( "toggle", Cvar_CompleteName ); + Cmd_SetAutoCompletion( "set", Cvar_CompleteName ); + Cmd_SetAutoCompletion( "sets", Cvar_CompleteName ); + Cmd_SetAutoCompletion( "setu", Cvar_CompleteName ); + Cmd_SetAutoCompletion( "seta", Cvar_CompleteName ); + Cmd_SetAutoCompletion( "reset", Cvar_CompleteName ); } diff --git a/code/qcommon/files.cpp b/code/qcommon/files.cpp index 9ecf87b..3d788b0 100644 --- a/code/qcommon/files.cpp +++ b/code/qcommon/files.cpp @@ -1668,7 +1668,7 @@ static int FS_AddFileToList( const char *name, char *list[MAX_FOUND_FILES], int Returns a uniqued list of files that match the given criteria from all search paths */ -static char** FS_ListFilteredFiles( const char *path, const char *extension, const char* filter, int *numfiles ) +static char** FS_ListFilteredFiles( const char *path, const char *extension, const char* filter, int *numfiles, int filters ) { int nfiles; char *list[MAX_FOUND_FILES]; @@ -1706,7 +1706,7 @@ static char** FS_ListFilteredFiles( const char *path, const char *extension, con // for (search = fs_searchpaths ; search ; search = search->next) { // is the element a pak file? - if (search->pack) { + if (search->pack && !(filters & FS_FILTER_INPAK)) { //ZOID: If we are pure, don't search for files on paks that // aren't on the pure list @@ -1757,7 +1757,7 @@ static char** FS_ListFilteredFiles( const char *path, const char *extension, con nfiles = FS_AddFileToList( name + temp, list, nfiles ); } } - } else if (search->dir) { // scan for files in the filesystem + } else if (search->dir && !(filters & FS_FILTER_NOTINPAK)) { // scan for files in the filesystem char *netpath; int numSysFiles; char **sysFiles; @@ -1800,7 +1800,7 @@ FS_ListFiles ================= */ char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) { - return FS_ListFilteredFiles( path, extension, NULL, numfiles ); + return FS_ListFilteredFiles( path, extension, NULL, numfiles, 0 ); } /* @@ -2151,7 +2151,7 @@ static void FS_NewDir_f() Com_Printf( "---------------\n" ); - dirnames = FS_ListFilteredFiles( "", "", Cmd_Argv(1), &ndirs ); + dirnames = FS_ListFilteredFiles( "", "", Cmd_Argv(1), &ndirs, 0 ); FS_SortFileList(dirnames, ndirs); @@ -3051,7 +3051,7 @@ Handle based file calls for virtual machines ======================================================================================== */ -int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) { +int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) { int r; qbool sync; @@ -3099,7 +3099,7 @@ int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) { return r; } -int FS_FTell( fileHandle_t f ) { +int FS_FTell( fileHandle_t f ) { int pos; if (fsh[f].zipFile == qtrue) { pos = unztell(fsh[f].handleFiles.file.z); @@ -3109,18 +3109,18 @@ int FS_FTell( fileHandle_t f ) { return pos; } -void FS_Flush( fileHandle_t f ) { +void FS_Flush( fileHandle_t f ) { fflush(fsh[f].handleFiles.file.o); } -void FS_FilenameCompletion( const char *dir, const char *ext, - qbool stripExt, void(*callback)(const char *s) ) { +void FS_FilenameCompletion( const char *dir, const char *ext, qbool stripExt, + void(*callback)(const char *s), int filters ) { char **filenames; int nfiles; int i; char filename[ MAX_STRING_CHARS ]; - filenames = FS_ListFilteredFiles( dir, ext, NULL, &nfiles ); + filenames = FS_ListFilteredFiles( dir, ext, NULL, &nfiles, filters ); FS_SortFileList( filenames, nfiles ); diff --git a/code/qcommon/q_shared.h b/code/qcommon/q_shared.h index daff1d1..f76b7e1 100644 --- a/code/qcommon/q_shared.h +++ b/code/qcommon/q_shared.h @@ -175,6 +175,12 @@ typedef int clipHandle_t; #define ARRAY_LEN(x) (sizeof(x) / sizeof(*(x))) +// #define VALUE 42 +// STRINGIZE_NE(VALUE) -> "VALUE" +// STRINGIZE(VALUE) -> "42" +#define STRINGIZE_NE(x) #x // no expansion +#define STRINGIZE(x) STRINGIZE_NE(x) // with expansion + // angle indexes #define PITCH 0 // up / down #define YAW 1 // left / right diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index 95f9444..a01dbb0 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -344,20 +344,26 @@ then searches for a command or variable that matches the first token. */ typedef void (*xcommand_t) (void); +typedef void (*xcommandCompletion_t) (int startArg, int compArg); void Cmd_Init(); -void Cmd_AddCommand( const char *cmd_name, xcommand_t function ); // called by the init functions of other parts of the program to // register commands and functions to call for them. // The cmd_name is referenced later, so it should not be in temp memory // if function is NULL, the command will be forwarded to the server // as a clc_clientCommand instead of executed locally +void Cmd_AddCommand( const char* cmd_name, xcommand_t function ); -void Cmd_RemoveCommand( const char *cmd_name ); +void Cmd_RemoveCommand( const char* cmd_name ); -void Cmd_CommandCompletion( void(*callback)(const char *s) ); +// auto-completion of command arguments +void Cmd_SetAutoCompletion( const char* cmd_name, xcommandCompletion_t complete ); +void Cmd_AutoCompleteArgument( const char* cmd_name, int startArg, int compArg ); + +// auto-completion of the command's name // callback with each valid string +void Cmd_CommandCompletion( void(*callback)(const char* s) ); // the functions that execute commands get their parameters with these // if arg > argc, Cmd_Argv() will return "", not NULL, so string ops are always safe @@ -365,6 +371,8 @@ int Cmd_Argc(); const char* Cmd_Argv(int arg); const char* Cmd_Args(); const char* Cmd_ArgsFrom( int arg ); +qbool Cmd_ArgQuoted( int arg ); +int Cmd_ArgIndexFromOffset( int offset ); // offset into the Cmd_TokenizeString argument void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ); void Cmd_ArgsBuffer( char *buffer, int bufferLength ); const char* Cmd_Cmd(); // note: this is NOT argv[0], it's the entire cmd as a raw string @@ -626,8 +634,11 @@ void FS_Rename( const char *from, const char *to ); void FS_Remove( const char *osPath ); void FS_HomeRemove( const char *homePath ); -void FS_FilenameCompletion( const char *dir, const char *ext, - qbool stripExt, void(*callback)(const char *s) ); +#define FS_FILTER_INPAK (1 << 0) +#define FS_FILTER_NOTINPAK (1 << 1) + +void FS_FilenameCompletion( const char *dir, const char *ext, qbool stripExt, + void (*callback)(const char *s), int filters ); /* @@ -647,7 +658,15 @@ typedef struct { } field_t; void Field_Clear( field_t *edit ); -void Field_AutoComplete( field_t *edit ); +void Field_AutoComplete( field_t *edit ); // should only be called by Console_Key + +// these are the functions you can use from your own command argument auto-completion callbacks +void Field_AutoCompleteFrom( int startArg, int compArg, qbool searchCmds, qbool searchVars ); +void Field_AutoCompleteMapName( int startArg, int compArg ); +void Field_AutoCompleteConfigName( int startArg, int compArg ); +void Field_AutoCompleteDemoNameRead( int startArg, int compArg ); +void Field_AutoCompleteDemoNameWrite( int startArg, int compArg ); +void Field_AutoCompleteKeyName( int startArg, int compArg ); /* ============================================================== @@ -853,6 +872,9 @@ void CL_FlushMemory(); void CL_StartHunkUsers( void ); // start all the client stuff using the hunk +void Key_KeyNameCompletion( void (*callback)(const char *s) ); +// for /bind and /unbind auto-completion + void Key_WriteBindings( fileHandle_t f ); // for writing the config files diff --git a/code/server/sv_ccmds.cpp b/code/server/sv_ccmds.cpp index 91fe37e..5b3d6ba 100644 --- a/code/server/sv_ccmds.cpp +++ b/code/server/sv_ccmds.cpp @@ -178,6 +178,13 @@ static void SV_DevMap_f( ) } +static void SV_CompleteMap_f( int startArg, int compArg ) +{ + if ( startArg + 1 == compArg ) + Field_AutoCompleteMapName( startArg, compArg ); +} + + /* Completely restarts a level, but doesn't send a new gamestate to the clients. This allows fair starts with variable load times. @@ -690,6 +697,8 @@ void SV_AddOperatorCommands() Cmd_AddCommand ("sectorlist", SV_SectorList_f); Cmd_AddCommand ("map", SV_Map_f); Cmd_AddCommand ("devmap", SV_DevMap_f); + Cmd_SetAutoCompletion ("map", SV_CompleteMap_f); + Cmd_SetAutoCompletion ("devmap", SV_CompleteMap_f); Cmd_AddCommand ("killserver", SV_KillServer_f); Cmd_AddCommand ("sv_restart", SV_ServerRestart_f ); if( com_dedicated->integer ) {