aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortma <tma@edf5b092-35ff-0310-97b2-ce42778d08ea>2006-01-22 01:58:50 +0000
committertma <tma@edf5b092-35ff-0310-97b2-ce42778d08ea>2006-01-22 01:58:50 +0000
commit670a7d99911d0663c5db663ed74019dbbb12a498 (patch)
tree047307e87cea2d216b1fd0a0cad92a591a5bd1e4
parentb411794fc8e0dc0e2d7ec044f813cfbe42bb8865 (diff)
downloadioquake3-aero-670a7d99911d0663c5db663ed74019dbbb12a498.tar.gz
ioquake3-aero-670a7d99911d0663c5db663ed74019dbbb12a498.zip
* 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
-rw-r--r--code/client/cl_keys.c4
-rw-r--r--code/qcommon/cmd.c23
-rw-r--r--code/qcommon/common.c263
-rw-r--r--code/qcommon/files.c23
-rw-r--r--code/qcommon/q_shared.c79
-rw-r--r--code/qcommon/q_shared.h2
-rw-r--r--code/qcommon/qcommon.h5
-rw-r--r--code/unix/unix_main.c18
8 files changed, 327 insertions, 90 deletions
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 "<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, " " );
- Cmd_CommandCompletion( FindMatches );
- Cvar_CommandCompletion( FindMatches );
+ 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 ( 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;
}