* 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
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;
}

View file

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

View file

@ -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;
str = strstr(src, start);
if (!str) {
keyConcatArgs();
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;
}
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;
// 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;
// only look at the first token for completion purposes
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 );
Field_CompleteCommand( completionField->buffer, qtrue, qtrue );
}

View file

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

View file

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

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, ...);
char *Com_SkipTokens( char *s, int numTokens, char *sep );
char *Com_SkipCharset( char *s, char *sep );
// mode parm for FS_FOpenFile
typedef enum {

View file

@ -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 );
/*
==============================================================

View file

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