From f15e2c2dcf76ccab07a429edd811ed7d7ac1d846 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 14 Jun 2018 21:19:04 +0300 Subject: [PATCH] Move command autocomplete to common engine files, as it used by Wcon and may be used by curses console in future --- engine/client/console.c | 278 ++------------------------------------ engine/common/common.h | 18 ++- engine/common/con_utils.c | 278 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 302 insertions(+), 272 deletions(-) diff --git a/engine/client/console.c b/engine/client/console.c index 684d42ab..924efae2 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -37,7 +37,6 @@ static qboolean g_utf8 = false; #define COLOR_DEFAULT '7' #define CON_HISTORY 64 #define MAX_DBG_NOTIFY 128 -#define CON_MAXCMDS 4096 // auto-complete intermediate list #define CON_NUMFONTS 3 // maxfonts #define CON_LINES( i ) (con.lines[(con.lines_first + (i)) % con.maxlines]) @@ -60,14 +59,6 @@ rgba_t g_color_table[8] = { 240, 180, 24, 255 }, // default color (can be changed by user) }; -typedef struct -{ - string buffer; - int cursor; - int scroll; - int widthInChars; -} field_t; - typedef struct { string szNotify; @@ -126,14 +117,6 @@ typedef struct notify_t notify[MAX_DBG_NOTIFY]; // for Con_NXPrintf qboolean draw_notify; // true if we have NXPrint message - - // console auto-complete - string shortestMatch; - field_t *completionField; // con.input or dedicated server fake field-line - const char *completionString; - const char *completionBuffer; - char *cmds[CON_MAXCMDS]; - int matchCount; } console_t; static console_t con; @@ -193,18 +176,6 @@ void Con_ClearNotify( void ) CON_LINES( i ).addtime = 0.0; } -/* -================ -Con_ClearField -================ -*/ -void Con_ClearField( field_t *edit ) -{ - memset( edit->buffer, 0, MAX_STRING ); - edit->cursor = 0; - edit->scroll = 0; -} - /* ================ Con_ClearTyping @@ -217,13 +188,7 @@ void Con_ClearTyping( void ) Con_ClearField( &con.input ); con.input.widthInChars = con.linewidth; - // free the old autocomplete list - for( i = 0; i < con.matchCount; i++ ) - { - freestring( con.cmds[i] ); - } - - con.matchCount = 0; + Cmd_AutoCompleteClear(); } /* @@ -1368,216 +1333,15 @@ EDIT FIELDS ============================================================================= */ /* -=============== -Con_AddCommandToList - -=============== -*/ -static void Con_AddCommandToList( const char *s, const char *unused1, const char *unused2, void *unused3 ) -{ - if( *s == '@' ) return; // never show system cvars or cmds - if( con.matchCount >= CON_MAXCMDS ) return; // list is full - - if( Q_strnicmp( s, con.completionString, Q_strlen( con.completionString ))) - return; // no match - - con.cmds[con.matchCount++] = copystring( s ); -} - -/* -================= -Con_SortCmds -================= -*/ -static int Con_SortCmds( const char **arg1, const char **arg2 ) -{ - return Q_stricmp( *arg1, *arg2 ); -} - -/* -=============== -Con_PrintCmdMatches -=============== -*/ -static void Con_PrintCmdMatches( const char *s, const char *unused1, const char *m, void *unused2 ) -{ - if( !Q_strnicmp( s, con.shortestMatch, Q_strlen( con.shortestMatch ))) - { - if( COM_CheckString( m )) Con_Printf( " %s ^3\"%s\"\n", s, m ); - else Con_Printf( " %s\n", s ); // variable or command without description - } -} - -/* -=============== -Con_PrintCvarMatches -=============== -*/ -static void Con_PrintCvarMatches( const char *s, const char *value, const char *m, void *unused2 ) -{ - if( !Q_strnicmp( s, con.shortestMatch, Q_strlen( con.shortestMatch ))) - { - if( COM_CheckString( m )) Con_Printf( " %s (%s) ^3\"%s\"\n", s, value, m ); - else Con_Printf( " %s (%s)\n", s, value ); // variable or command without description - } -} - -/* -=============== -Con_ConcatRemaining -=============== -*/ -static void Con_ConcatRemaining( const char *src, const char *start ) -{ - const char *arg; - int i; - - arg = Q_strstr( src, start ); - - if( !arg ) - { - for( i = 1; i < Cmd_Argc(); i++ ) - { - Q_strncat( con.completionField->buffer, " ", sizeof( con.completionField->buffer )); - arg = Cmd_Argv( i ); - while( *arg ) - { - if( *arg == ' ' ) - { - Q_strncat( con.completionField->buffer, "\"", sizeof( con.completionField->buffer )); - break; - } - arg++; - } - - Q_strncat( con.completionField->buffer, Cmd_Argv( i ), sizeof( con.completionField->buffer )); - if( *arg == ' ' ) Q_strncat( con.completionField->buffer, "\"", sizeof( con.completionField->buffer )); - } - return; - } - - arg += Q_strlen( start ); - Q_strncat( con.completionField->buffer, arg, sizeof( con.completionField->buffer )); -} - -/* -=============== -Con_CompleteCommand - -perform Tab expansion -=============== +================ +Con_ClearField +================ */ -void Con_CompleteCommand( field_t *field ) +void Con_ClearField(field_t *edit) { - field_t temp; - string filename; - qboolean nextcmd; - int i; - - // setup the completion field - con.completionField = field; - - // only look at the first token for completion purposes - Cmd_TokenizeString( con.completionField->buffer ); - - nextcmd = ( con.completionField->buffer[Q_strlen( con.completionField->buffer ) - 1] == ' ' ) ? true : false; - - con.completionString = Cmd_Argv( 0 ); - con.completionBuffer = Cmd_Argv( 1 ); - - // skip backslash - while( *con.completionString && ( *con.completionString == '\\' || *con.completionString == '/' )) - con.completionString++; - - // skip backslash - while( *con.completionBuffer && ( *con.completionBuffer == '\\' || *con.completionBuffer == '/' )) - con.completionBuffer++; - - if( !Q_strlen( con.completionString )) - return; - - // free the old autocomplete list - for( i = 0; i < con.matchCount; i++ ) - { - if( con.cmds[i] != NULL ) - { - Mem_Free( con.cmds[i] ); - con.cmds[i] = NULL; - } - } - - con.matchCount = 0; - con.shortestMatch[0] = 0; - - // find matching commands and variables - Cmd_LookupCmds( NULL, NULL, Con_AddCommandToList ); - Cvar_LookupVars( 0, NULL, NULL, Con_AddCommandToList ); - - if( !con.matchCount ) return; // no matches - - memcpy( &temp, con.completionField, sizeof( field_t )); - - // autocomplete second arg - if(( Cmd_Argc() == 2 ) || (( Cmd_Argc() == 1 ) && nextcmd )) - { - if( !Q_strlen( con.completionBuffer )) - return; - - if( Cmd_AutocompleteName( con.completionBuffer, filename, sizeof( filename ))) - { - Q_sprintf( con.completionField->buffer, "%s %s", Cmd_Argv( 0 ), filename ); - con.completionField->cursor = Q_strlen( con.completionField->buffer ); - } - - // don't adjusting cursor pos if we nothing found - return; - } - else if( Cmd_Argc() >= 3 ) - { - // disable autocomplete for all next args - return; - } - - if( con.matchCount == 1 ) - { - Q_sprintf( con.completionField->buffer, "\\%s", con.cmds[0] ); - if( Cmd_Argc() == 1 ) Q_strncat( con.completionField->buffer, " ", sizeof( con.completionField->buffer )); - else Con_ConcatRemaining( temp.buffer, con.completionString ); - con.completionField->cursor = Q_strlen( con.completionField->buffer ); - } - else - { - char *first, *last; - int len = 0; - - qsort( con.cmds, con.matchCount, sizeof( char* ), Con_SortCmds ); - - // find the number of matching characters between the first and - // the last element in the list and copy it - first = con.cmds[0]; - last = con.cmds[con.matchCount-1]; - - while( *first && *last && Q_tolower( *first ) == Q_tolower( *last )) - { - first++; - last++; - - con.shortestMatch[len] = con.cmds[0][len]; - len++; - } - con.shortestMatch[len] = 0; - - // multiple matches, complete to shortest - Q_sprintf( con.completionField->buffer, "\\%s", con.shortestMatch ); - con.completionField->cursor = Q_strlen( con.completionField->buffer ); - Con_ConcatRemaining( temp.buffer, con.completionString ); - - Con_Printf( "]%s\n", con.completionField->buffer ); - - // run through again, printing matches - Cmd_LookupCmds( NULL, NULL, Con_PrintCmdMatches ); - Cvar_LookupVars( 0, NULL, NULL, Con_PrintCvarMatches ); - } + memset(edit->buffer, 0, MAX_STRING); + edit->cursor = 0; + edit->scroll = 0; } /* @@ -2563,32 +2327,6 @@ void Con_InvalidateFonts( void ) con.curFont = con.lastUsedFont = NULL; } -/* -========= -Cmd_AutoComplete - -NOTE: input string must be equal or longer than MAX_STRING -========= -*/ -void Cmd_AutoComplete( char *complete_string ) -{ - field_t input; - - if( !complete_string || !*complete_string ) - return; - - // setup input - Q_strncpy( input.buffer, complete_string, sizeof( input.buffer )); - input.cursor = input.scroll = 0; - - Con_CompleteCommand( &input ); - - // setup output - if( input.buffer[0] == '\\' || input.buffer[0] == '/' ) - Q_strncpy( complete_string, input.buffer + 1, sizeof( input.buffer )); - else Q_strncpy( complete_string, input.buffer, sizeof( input.buffer )); -} - /* ========= Con_FastClose diff --git a/engine/common/common.h b/engine/common/common.h index 969a53f6..3ed50ea1 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -364,6 +364,15 @@ typedef enum #include "net_ws.h" +// console field +typedef struct +{ + string buffer; + int cursor; + int scroll; + int widthInChars; +} field_t; + typedef struct host_redirect_s { rdtype_t target; @@ -892,6 +901,13 @@ void pfnResetTutorMessageDecayData( void ); #define Z_Realloc( ptr, size ) Mem_Realloc( host.mempool, ptr, size ) #define Z_Free( ptr ) if( ptr != NULL ) Mem_Free( ptr ) +// +// con_utils.c +// +qboolean Cmd_AutocompleteName( const char *source, char *buffer, size_t bufsize ); +void Cmd_AutoComplete( char *complete_string ); +void Cmd_AutoCompleteClear( void ); + // // crclib.c // @@ -1053,8 +1069,6 @@ void Info_WriteVars( file_t *f ); void Info_Print( const char *s ); void Cmd_WriteVariables( file_t *f ); int Cmd_CheckMapsList( int fRefresh ); -qboolean Cmd_AutocompleteName( const char *source, char *buffer, size_t bufsize ); -void Cmd_AutoComplete( char *complete_string ); void COM_SetRandomSeed( long lSeed ); int COM_RandomLong( int lMin, int lMax ); float COM_RandomFloat( float fMin, float fMax ); diff --git a/engine/common/con_utils.c b/engine/common/con_utils.c index f977468f..6541c780 100644 --- a/engine/common/con_utils.c +++ b/engine/common/con_utils.c @@ -20,16 +20,32 @@ GNU General Public License for more details. extern convar_t *con_gamemaps; +#define CON_MAXCMDS 4096 // auto-complete intermediate list + typedef struct autocomplete_list_s { const char *name; qboolean (*func)( const char *s, char *name, int length ); } autocomplete_list_t; +typedef struct +{ + // console auto-complete + string shortestMatch; + field_t *completionField; // con.input or dedicated server fake field-line + const char *completionString; + const char *completionBuffer; + char *cmds[CON_MAXCMDS]; + int matchCount; +} con_autocomplete_t; + +static con_autocomplete_t con; + /* ======================================================================= FILENAME AUTOCOMPLETION + ======================================================================= */ /* @@ -869,6 +885,268 @@ qboolean Cmd_AutocompleteName( const char *source, char *buffer, size_t bufsize return false; } +/* +=============== +Con_AddCommandToList + +=============== +*/ +static void Con_AddCommandToList( const char *s, const char *unused1, const char *unused2, void *unused3 ) +{ + if( *s == '@' ) return; // never show system cvars or cmds + if( con.matchCount >= CON_MAXCMDS ) return; // list is full + + if( Q_strnicmp( s, con.completionString, Q_strlen( con.completionString ) ) ) + return; // no match + + con.cmds[con.matchCount++] = copystring( s ); +} + +/* +================= +Con_SortCmds +================= +*/ +static int Con_SortCmds( const char **arg1, const char **arg2 ) +{ + return Q_stricmp( *arg1, *arg2 ); +} + +/* +=============== +Con_PrintCmdMatches +=============== +*/ +static void Con_PrintCmdMatches( const char *s, const char *unused1, const char *m, void *unused2 ) +{ + if( !Q_strnicmp( s, con.shortestMatch, Q_strlen( con.shortestMatch ) ) ) + { + if( COM_CheckString( m ) ) Con_Printf( " %s ^3\"%s\"\n", s, m ); + else Con_Printf( " %s\n", s ); // variable or command without description + } +} + +/* +=============== +Con_PrintCvarMatches +=============== +*/ +static void Con_PrintCvarMatches( const char *s, const char *value, const char *m, void *unused2 ) +{ + if( !Q_strnicmp( s, con.shortestMatch, Q_strlen( con.shortestMatch ) ) ) + { + if( COM_CheckString( m ) ) Con_Printf( " %s (%s) ^3\"%s\"\n", s, value, m ); + else Con_Printf( " %s (%s)\n", s, value ); // variable or command without description + } +} + +/* +=============== +Con_ConcatRemaining +=============== +*/ +static void Con_ConcatRemaining( const char *src, const char *start ) +{ + const char *arg; + int i; + + arg = Q_strstr( src, start ); + + if( !arg ) + { + for( i = 1; i < Cmd_Argc(); i++ ) + { + Q_strncat( con.completionField->buffer, " ", sizeof( con.completionField->buffer ) ); + arg = Cmd_Argv( i ); + while( *arg ) + { + if( *arg == ' ' ) + { + Q_strncat( con.completionField->buffer, "\"", sizeof( con.completionField->buffer ) ); + break; + } + arg++; + } + + Q_strncat( con.completionField->buffer, Cmd_Argv( i ), sizeof( con.completionField->buffer ) ); + if( *arg == ' ' ) Q_strncat( con.completionField->buffer, "\"", sizeof( con.completionField->buffer ) ); + } + return; + } + + arg += Q_strlen( start ); + Q_strncat( con.completionField->buffer, arg, sizeof( con.completionField->buffer ) ); +} + +/* +=============== +Con_CompleteCommand + +perform Tab expansion +=============== +*/ +void Con_CompleteCommand( field_t *field ) +{ + field_t temp; + string filename; + qboolean nextcmd; + int i; + + // setup the completion field + con.completionField = field; + + // only look at the first token for completion purposes + Cmd_TokenizeString( con.completionField->buffer ); + + nextcmd = (con.completionField->buffer[Q_strlen( con.completionField->buffer ) - 1] == ' ') ? true : false; + + con.completionString = Cmd_Argv( 0 ); + con.completionBuffer = Cmd_Argv( 1 ); + + // skip backslash + while( *con.completionString && (*con.completionString == '\\' || *con.completionString == '/') ) + con.completionString++; + + // skip backslash + while( *con.completionBuffer && (*con.completionBuffer == '\\' || *con.completionBuffer == '/') ) + con.completionBuffer++; + + if( !Q_strlen( con.completionString ) ) + return; + + // free the old autocomplete list + for( i = 0; i < con.matchCount; i++ ) + { + if( con.cmds[i] != NULL ) + { + Mem_Free( con.cmds[i] ); + con.cmds[i] = NULL; + } + } + + con.matchCount = 0; + con.shortestMatch[0] = 0; + + // find matching commands and variables + Cmd_LookupCmds( NULL, NULL, Con_AddCommandToList ); + Cvar_LookupVars( 0, NULL, NULL, Con_AddCommandToList ); + + if( !con.matchCount ) return; // no matches + + memcpy( &temp, con.completionField, sizeof( field_t ) ); + + // autocomplete second arg + if( (Cmd_Argc() == 2) || ((Cmd_Argc() == 1) && nextcmd) ) + { + if( !Q_strlen( con.completionBuffer ) ) + return; + + if( Cmd_AutocompleteName( con.completionBuffer, filename, sizeof( filename ) ) ) + { + Q_sprintf( con.completionField->buffer, "%s %s", Cmd_Argv( 0 ), filename ); + con.completionField->cursor = Q_strlen( con.completionField->buffer ); + } + + // don't adjusting cursor pos if we nothing found + return; + } + else if( Cmd_Argc() >= 3 ) + { + // disable autocomplete for all next args + return; + } + + if( con.matchCount == 1 ) + { + Q_sprintf( con.completionField->buffer, "\\%s", con.cmds[0] ); + if( Cmd_Argc() == 1 ) Q_strncat( con.completionField->buffer, " ", sizeof( con.completionField->buffer ) ); + else Con_ConcatRemaining( temp.buffer, con.completionString ); + con.completionField->cursor = Q_strlen( con.completionField->buffer ); + } + else + { + char *first, *last; + int len = 0; + + qsort( con.cmds, con.matchCount, sizeof( char* ), Con_SortCmds ); + + // find the number of matching characters between the first and + // the last element in the list and copy it + first = con.cmds[0]; + last = con.cmds[con.matchCount - 1]; + + while( *first && *last && Q_tolower( *first ) == Q_tolower( *last ) ) + { + first++; + last++; + + con.shortestMatch[len] = con.cmds[0][len]; + len++; + } + con.shortestMatch[len] = 0; + + // multiple matches, complete to shortest + Q_sprintf( con.completionField->buffer, "\\%s", con.shortestMatch ); + con.completionField->cursor = Q_strlen( con.completionField->buffer ); + Con_ConcatRemaining( temp.buffer, con.completionString ); + + Con_Printf( "]%s\n", con.completionField->buffer ); + + // run through again, printing matches + Cmd_LookupCmds( NULL, NULL, Con_PrintCmdMatches ); + Cvar_LookupVars( 0, NULL, NULL, Con_PrintCvarMatches ); + } +} + +/* +========= +Cmd_AutoComplete + +NOTE: input string must be equal or longer than MAX_STRING +========= +*/ +void Cmd_AutoComplete( char *complete_string ) +{ + field_t input; + + if( !complete_string || !*complete_string ) + return; + + // setup input + Q_strncpy( input.buffer, complete_string, sizeof( input.buffer ) ); + input.cursor = input.scroll = 0; + + Con_CompleteCommand( &input ); + + // setup output + if( input.buffer[0] == '\\' || input.buffer[0] == '/' ) + Q_strncpy( complete_string, input.buffer + 1, sizeof( input.buffer ) ); + else Q_strncpy( complete_string, input.buffer, sizeof( input.buffer ) ); +} + +/* +============ +Cmd_AutoCompleteClear + +============ +*/ +void Cmd_AutoCompleteClear( void ) +{ + int i; + + // free the old autocomplete list + for( i = 0; i < con.matchCount; i++ ) + { + if( con.cmds[i] != NULL ) + { + Mem_Free( con.cmds[i] ); + con.cmds[i] = NULL; + } + } + + con.matchCount = 0; +} + /* ============ Cmd_WriteVariables