* 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
This commit is contained in:
Tim Angus 2006-01-22 01:58:50 +00:00
parent 893629fb0f
commit c3f7915a8b
8 changed files with 340 additions and 103 deletions

View file

@ -483,7 +483,7 @@ void Console_Key (int key) {
// enter finishes the line // enter finishes the line
if ( key == K_ENTER || key == K_KP_ENTER ) { 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] != '\\' if ( cls.state != CA_ACTIVE && g_consoleField.buffer[0] != '\\'
&& g_consoleField.buffer[0] != '/' ) { && g_consoleField.buffer[0] != '/' ) {
char temp[MAX_STRING_CHARS]; char temp[MAX_STRING_CHARS];
@ -528,7 +528,7 @@ void Console_Key (int key) {
// command completion // command completion
if (key == K_TAB) { if (key == K_TAB) {
Field_CompleteCommand(&g_consoleField); Field_AutoComplete(&g_consoleField);
return; return;
} }

View file

@ -439,7 +439,7 @@ will point into this temporary buffer.
*/ */
// NOTE TTimo define that to track tokenization issues // NOTE TTimo define that to track tokenization issues
//#define TKN_DBG //#define TKN_DBG
void Cmd_TokenizeString( const char *text_in ) { static void Cmd_TokenizeString2( const char *text_in, qboolean ignoreQuotes ) {
const char *text; const char *text;
char *textOut; char *textOut;
@ -495,7 +495,7 @@ void Cmd_TokenizeString( const char *text_in ) {
// handle quoted strings // handle quoted strings
// NOTE TTimo this doesn't handle \" escaping // NOTE TTimo this doesn't handle \" escaping
if ( *text == '"' ) { if ( !ignoreQuotes && *text == '"' ) {
cmd_argv[cmd_argc] = textOut; cmd_argv[cmd_argc] = textOut;
cmd_argc++; cmd_argc++;
text++; text++;
@ -516,7 +516,7 @@ void Cmd_TokenizeString( const char *text_in ) {
// skip until whitespace, quote, or command // skip until whitespace, quote, or command
while ( *text > ' ' ) { while ( *text > ' ' ) {
if ( text[0] == '"' ) { if ( !ignoreQuotes && text[0] == '"' ) {
break; 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 );
}
/* /*
============ ============

View file

@ -2912,7 +2912,7 @@ void Field_Clear( field_t *edit ) {
static const char *completionString; static const char *completionString;
static char shortestMatch[MAX_TOKEN_CHARS]; static char shortestMatch[MAX_TOKEN_CHARS];
static int matchCount; 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; 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 ) ) ) { if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) {
Com_Printf( " %s\n", s ); 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++ ) { for( i = 0; i < strlen( s ); i++ )
Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); {
arg = Cmd_Argv( i ); if( s[ i ] == ';' )
while (*arg) { return &s[ i ];
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 ), "\"");
}
} }
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;
str = strstr(src, start); FS_FilenameCompletion( dir, ext, stripExt, FindMatches );
if (!str) {
keyConcatArgs(); 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; return;
} }
str += strlen(start); Com_Printf( "]%s\n", completionField->buffer );
Q_strcat( completionField->buffer, sizeof( completionField->buffer ), str);
FS_FilenameCompletion( dir, ext, stripExt, PrintMatches );
} }
/* /*
=============== ===============
Field_CompleteCommand 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 ) { static void Field_CompleteCommand( char *cmd,
field_t temp; qboolean doCommands, qboolean 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; completionField = field;
// only look at the first token for completion purposes Field_CompleteCommand( completionField->buffer, qtrue, qtrue );
Cmd_TokenizeString( completionField->buffer );
completionString = Cmd_Argv(0);
if ( completionString[0] == '\\' || completionString[0] == '/' ) {
completionString++;
}
matchCount = 0;
shortestMatch[0] = 0;
if ( strlen( completionString ) == 0 ) {
return;
}
Cmd_CommandCompletion( FindMatches );
Cvar_CommandCompletion( FindMatches );
if ( matchCount == 0 ) {
return; // no matches
}
Com_Memcpy(&temp, completionField, sizeof(field_t));
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 );
}
completionField->cursor = strlen( completionField->buffer );
return;
}
// multiple matches, complete to shortest
Com_sprintf( completionField->buffer, sizeof( completionField->buffer ), "\\%s", shortestMatch );
completionField->cursor = strlen( completionField->buffer );
ConcatRemaining( temp.buffer, completionString );
Com_Printf( "]%s\n", completionField->buffer );
// run through again, printing matches
Cmd_CommandCompletion( PrintCmdMatches );
Cvar_CommandCompletion( PrintCvarMatches );
} }

View file

@ -3426,3 +3426,26 @@ void FS_Flush( fileHandle_t f ) {
fflush(fsh[f].handleFiles.file.o); 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 );
}

View file

@ -59,10 +59,19 @@ COM_StripExtension
============ ============
*/ */
void COM_StripExtension( const char *in, char *out ) { void COM_StripExtension( const char *in, char *out ) {
while ( *in && *in != '.' ) { int length;
*out++ = *in++;
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;
}

View file

@ -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, ...); 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 // mode parm for FS_FOpenFile
typedef enum { typedef enum {

View file

@ -415,6 +415,7 @@ char *Cmd_Cmd (void);
// if arg > argc, so string operations are allways safe. // if arg > argc, so string operations are allways safe.
void Cmd_TokenizeString( const char *text ); 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. // Takes a null terminated string. Does not need to be /n terminated.
// breaks the string up into arg tokens. // 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_Remove( const char *osPath );
void FS_HomeRemove( const char *homePath ); 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; } field_t;
void Field_Clear( field_t *edit ); void Field_Clear( field_t *edit );
void Field_CompleteCommand( field_t *edit ); void Field_AutoComplete( field_t *edit );
/* /*
============================================================== ==============================================================

View file

@ -549,7 +549,6 @@ char *Sys_ConsoleInput(void)
{ {
// we use this when sending back commands // we use this when sending back commands
static char text[256]; static char text[256];
int i;
int avail; int avail;
char key; char key;
field_t *history; field_t *history;
@ -588,22 +587,7 @@ char *Sys_ConsoleInput(void)
if (key == '\t') if (key == '\t')
{ {
tty_Hide(); tty_Hide();
Field_CompleteCommand( &tty_con ); Field_AutoComplete( &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--;
}
}
tty_Show(); tty_Show();
return NULL; return NULL;
} }