mirror of
https://github.com/UberGames/ioef.git
synced 2024-11-27 22:42:09 +00:00
* 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:
parent
893629fb0f
commit
c3f7915a8b
8 changed files with 340 additions and 103 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
/*
|
||||
============
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 );
|
||||
|
||||
/*
|
||||
==============================================================
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue