intelligent TAB-completion handling (support for mapnames, binds, etc)

This commit is contained in:
arQon 2017-01-19 00:59:44 -08:00
parent 047f312ea7
commit 4f9754adfe
9 changed files with 436 additions and 424 deletions

View file

@ -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 );
}

View file

@ -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;

View file

@ -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 <variablename> : 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 );
}

View file

@ -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 "<cmd> "
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 <key> "
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 );
}

View file

@ -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 );
}

View file

@ -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 );

View file

@ -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

View file

@ -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

View file

@ -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 ) {