From 670a7d99911d0663c5db663ed74019dbbb12a498 Mon Sep 17 00:00:00 2001 From: tma Date: Sun, 22 Jan 2006 01:58:50 +0000 Subject: * Overhaul of console autocompletion - No longer does weird stuff like move the cursor inappropriately - Autocomplete works with compound commands - Special autocomplete on some commands e.g. \map, \demo - Removed various hacks used to counter the original autocomplete code git-svn-id: svn://svn.icculus.org/quake3/trunk@514 edf5b092-35ff-0310-97b2-ce42778d08ea --- code/client/cl_keys.c | 4 +- code/qcommon/cmd.c | 23 ++++- code/qcommon/common.c | 263 ++++++++++++++++++++++++++++++++++++------------ code/qcommon/files.c | 23 +++++ code/qcommon/q_shared.c | 79 ++++++++++++++- code/qcommon/q_shared.h | 2 + code/qcommon/qcommon.h | 5 +- code/unix/unix_main.c | 18 +--- 8 files changed, 327 insertions(+), 90 deletions(-) (limited to 'code') diff --git a/code/client/cl_keys.c b/code/client/cl_keys.c index a23b4ee..889ca82 100644 --- a/code/client/cl_keys.c +++ b/code/client/cl_keys.c @@ -483,7 +483,7 @@ void Console_Key (int key) { // enter finishes the line if ( key == K_ENTER || key == K_KP_ENTER ) { - // if not in the game explicitly prepent a slash if needed + // if not in the game explicitly prepend a slash if needed if ( cls.state != CA_ACTIVE && g_consoleField.buffer[0] != '\\' && g_consoleField.buffer[0] != '/' ) { char temp[MAX_STRING_CHARS]; @@ -528,7 +528,7 @@ void Console_Key (int key) { // command completion if (key == K_TAB) { - Field_CompleteCommand(&g_consoleField); + Field_AutoComplete(&g_consoleField); return; } diff --git a/code/qcommon/cmd.c b/code/qcommon/cmd.c index b1506f5..5af0f95 100644 --- a/code/qcommon/cmd.c +++ b/code/qcommon/cmd.c @@ -439,7 +439,7 @@ will point into this temporary buffer. */ // NOTE TTimo define that to track tokenization issues //#define TKN_DBG -void Cmd_TokenizeString( const char *text_in ) { +static void Cmd_TokenizeString2( const char *text_in, qboolean ignoreQuotes ) { const char *text; char *textOut; @@ -495,7 +495,7 @@ void Cmd_TokenizeString( const char *text_in ) { // handle quoted strings // NOTE TTimo this doesn't handle \" escaping - if ( *text == '"' ) { + if ( !ignoreQuotes && *text == '"' ) { cmd_argv[cmd_argc] = textOut; cmd_argc++; text++; @@ -516,7 +516,7 @@ void Cmd_TokenizeString( const char *text_in ) { // skip until whitespace, quote, or command while ( *text > ' ' ) { - if ( text[0] == '"' ) { + if ( !ignoreQuotes && text[0] == '"' ) { break; } @@ -541,6 +541,23 @@ void Cmd_TokenizeString( const char *text_in ) { } +/* +============ +Cmd_TokenizeString +============ +*/ +void Cmd_TokenizeString( const char *text_in ) { + Cmd_TokenizeString2( text_in, qfalse ); +} + +/* +============ +Cmd_TokenizeStringIgnoreQuotes +============ +*/ +void Cmd_TokenizeStringIgnoreQuotes( const char *text_in ) { + Cmd_TokenizeString2( text_in, qtrue ); +} /* ============ diff --git a/code/qcommon/common.c b/code/qcommon/common.c index ba95f09..eb46426 100644 --- a/code/qcommon/common.c +++ b/code/qcommon/common.c @@ -2912,7 +2912,7 @@ void Field_Clear( field_t *edit ) { static const char *completionString; static char shortestMatch[MAX_TOKEN_CHARS]; static int matchCount; -// field we are working on, passed to Field_CompleteCommand (&g_consoleCommand for instance) +// field we are working on, passed to Field_AutoComplete(&g_consoleCommand for instance) static field_t *completionField; /* @@ -2948,11 +2948,11 @@ static void FindMatches( const char *s ) { /* =============== -PrintCmdMatches +PrintMatches =============== */ -static void PrintCmdMatches( const char *s ) { +static void PrintMatches( const char *s ) { if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) { Com_Printf( " %s\n", s ); } @@ -2970,96 +2970,231 @@ static void PrintCvarMatches( const char *s ) { } } -static void keyConcatArgs( void ) { - int i; - char *arg; +/* +=============== +Field_FindFirstSeparator +=============== +*/ +static char *Field_FindFirstSeparator( char *s ) +{ + int i; - 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 ), "\""); - } + for( i = 0; i < strlen( s ); i++ ) + { + if( s[ i ] == ';' ) + return &s[ i ]; } + + return NULL; } -static void ConcatRemaining( const char *src, const char *start ) { - char *str; +/* +=============== +Field_CompleteFilename +=============== +*/ +static void Field_CompleteFilename( const char *dir, + const char *ext, qboolean stripExt ) +{ + matchCount = 0; + shortestMatch[ 0 ] = 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 ); - str = strstr(src, start); - if (!str) { - keyConcatArgs(); + if( matchCount == 1 ) + { + Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); + completionField->cursor++; return; } - str += strlen(start); - Q_strcat( completionField->buffer, sizeof( completionField->buffer ), str); + Com_Printf( "]%s\n", completionField->buffer ); + + FS_FilenameCompletion( dir, ext, stripExt, PrintMatches ); } /* =============== Field_CompleteCommand - -perform Tab expansion -NOTE TTimo this was originally client code only - moved to common code when writing tty console for *nix dedicated server =============== */ -void Field_CompleteCommand( field_t *field ) { - field_t temp; +static void Field_CompleteCommand( char *cmd, + qboolean doCommands, qboolean doCvars ) +{ + int completionArgument = 0; + char *p; - completionField = field; + // Skip leading whitespace and quotes + cmd = Com_SkipCharset( cmd, " \"" ); - // only look at the first token for completion purposes - Cmd_TokenizeString( completionField->buffer ); + Cmd_TokenizeStringIgnoreQuotes( cmd ); + completionArgument = Cmd_Argc( ); - completionString = Cmd_Argv(0); - if ( completionString[0] == '\\' || completionString[0] == '/' ) { - completionString++; + // If there is trailing whitespace on the cmd + if( *( cmd + strlen( cmd ) - 1 ) == ' ' ) + { + completionString = ""; + completionArgument++; } - matchCount = 0; - shortestMatch[0] = 0; + else + completionString = Cmd_Argv( completionArgument - 1 ); - if ( strlen( completionString ) == 0 ) { - return; - } + 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, " " ); - Cmd_CommandCompletion( FindMatches ); - Cvar_CommandCompletion( FindMatches ); + if( p > cmd ) + Field_CompleteCommand( p, qtrue, qtrue ); + } + else if( !Q_stricmp( baseCmd, "bind" ) && completionArgument >= 3 ) + { + // Skip "bind " + p = Com_SkipTokens( cmd, 2, " " ); - if ( matchCount == 0 ) { - return; // no matches + if( p > cmd ) + Field_CompleteCommand( p, qtrue, qtrue ); + } + } } + else + { + if( completionString[0] == '\\' || completionString[0] == '/' ) + completionString++; - Com_Memcpy(&temp, completionField, sizeof(field_t)); + matchCount = 0; + shortestMatch[ 0 ] = 0; - if ( matchCount == 1 ) { - Com_sprintf( completionField->buffer, sizeof( completionField->buffer ), "\\%s", shortestMatch ); - if ( Cmd_Argc() == 1 ) { - Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); - } else { - ConcatRemaining( temp.buffer, completionString ); + 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 ); - return; + + 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 ); } +} - // multiple matches, complete to shortest - Com_sprintf( completionField->buffer, sizeof( completionField->buffer ), "\\%s", shortestMatch ); - completionField->cursor = strlen( completionField->buffer ); - ConcatRemaining( temp.buffer, completionString ); +/* +=============== +Field_AutoComplete - Com_Printf( "]%s\n", completionField->buffer ); +Perform Tab expansion +=============== +*/ +void Field_AutoComplete( field_t *field ) +{ + completionField = field; - // run through again, printing matches - Cmd_CommandCompletion( PrintCmdMatches ); - Cvar_CommandCompletion( PrintCvarMatches ); + Field_CompleteCommand( completionField->buffer, qtrue, qtrue ); } diff --git a/code/qcommon/files.c b/code/qcommon/files.c index a4fa79d..3d85c7c 100644 --- a/code/qcommon/files.c +++ b/code/qcommon/files.c @@ -3426,3 +3426,26 @@ void FS_Flush( fileHandle_t f ) { fflush(fsh[f].handleFiles.file.o); } +void FS_FilenameCompletion( const char *dir, const char *ext, + qboolean stripExt, void(*callback)(const char *s) ) { + char **filenames; + int nfiles; + int i; + char filename[ MAX_STRING_CHARS ]; + + filenames = FS_ListFilteredFiles( dir, ext, NULL, &nfiles ); + + FS_SortFileList( filenames, nfiles ); + + for( i = 0; i < nfiles; i++ ) { + FS_ConvertPath( filenames[ i ] ); + Q_strncpyz( filename, filenames[ i ], MAX_STRING_CHARS ); + + if( stripExt ) { + COM_StripExtension( filename, filename ); + } + + callback( filename ); + } + FS_FreeFileList( filenames ); +} diff --git a/code/qcommon/q_shared.c b/code/qcommon/q_shared.c index af72ec2..2100390 100644 --- a/code/qcommon/q_shared.c +++ b/code/qcommon/q_shared.c @@ -59,10 +59,19 @@ COM_StripExtension ============ */ void COM_StripExtension( const char *in, char *out ) { - while ( *in && *in != '.' ) { - *out++ = *in++; + int length; + + strcpy( out, in ); + + length = strlen(out)-1; + while (length > 0 && out[length] != '.') + { + length--; + if (out[length] == '/') + return; // no extension } - *out = 0; + if (length) + out[length] = 0; } @@ -1249,4 +1258,68 @@ void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) { //==================================================================== +/* +================== +Com_CharIsOneOfCharset +================== +*/ +static qboolean Com_CharIsOneOfCharset( char c, char *set ) +{ + int i; + for( i = 0; i < strlen( set ); i++ ) + { + if( set[ i ] == c ) + return qtrue; + } + + return qfalse; +} + +/* +================== +Com_SkipCharset +================== +*/ +char *Com_SkipCharset( char *s, char *sep ) +{ + char *p = s; + + while( p ) + { + if( Com_CharIsOneOfCharset( *p, sep ) ) + p++; + else + break; + } + + return p; +} + +/* +================== +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; + } + + if( sepCount == numTokens ) + return p; + else + return s; +} diff --git a/code/qcommon/q_shared.h b/code/qcommon/q_shared.h index e80074d..2dc5500 100644 --- a/code/qcommon/q_shared.h +++ b/code/qcommon/q_shared.h @@ -610,6 +610,8 @@ void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m); void QDECL Com_sprintf (char *dest, int size, const char *fmt, ...); +char *Com_SkipTokens( char *s, int numTokens, char *sep ); +char *Com_SkipCharset( char *s, char *sep ); // mode parm for FS_FOpenFile typedef enum { diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index 1b2fc0b..21c74e9 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -415,6 +415,7 @@ char *Cmd_Cmd (void); // if arg > argc, so string operations are allways safe. void Cmd_TokenizeString( const char *text ); +void Cmd_TokenizeStringIgnoreQuotes( const char *text_in ); // Takes a null terminated string. Does not need to be /n terminated. // breaks the string up into arg tokens. @@ -657,6 +658,8 @@ 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, + qboolean stripExt, void(*callback)(const char *s) ); /* ============================================================== @@ -674,7 +677,7 @@ typedef struct { } field_t; void Field_Clear( field_t *edit ); -void Field_CompleteCommand( field_t *edit ); +void Field_AutoComplete( field_t *edit ); /* ============================================================== diff --git a/code/unix/unix_main.c b/code/unix/unix_main.c index 4216b1c..8cd0886 100644 --- a/code/unix/unix_main.c +++ b/code/unix/unix_main.c @@ -549,7 +549,6 @@ char *Sys_ConsoleInput(void) { // we use this when sending back commands static char text[256]; - int i; int avail; char key; field_t *history; @@ -588,22 +587,7 @@ char *Sys_ConsoleInput(void) if (key == '\t') { tty_Hide(); - Field_CompleteCommand( &tty_con ); - // Field_CompleteCommand does weird things to the string, do a cleanup - // it adds a '\' at the beginning of the string - // cursor doesn't reflect actual length of the string that's sent back - tty_con.cursor = strlen(tty_con.buffer); - if (tty_con.cursor>0) - { - if (tty_con.buffer[0] == '\\') - { - for (i=0; i<=tty_con.cursor; i++) - { - tty_con.buffer[i] = tty_con.buffer[i+1]; - } - tty_con.cursor--; - } - } + Field_AutoComplete( &tty_con ); tty_Show(); return NULL; } -- cgit v1.2.3