summaryrefslogtreecommitdiff
path: root/src/prompt.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/prompt.c')
-rw-r--r--src/prompt.c577
1 files changed, 577 insertions, 0 deletions
diff --git a/src/prompt.c b/src/prompt.c
new file mode 100644
index 0000000..f106bf4
--- /dev/null
+++ b/src/prompt.c
@@ -0,0 +1,577 @@
+/*
+Copyright (C) 2003-2006 Andrey Nazarov
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+//
+// prompt.c
+//
+
+#include "com_local.h"
+#include "files.h"
+#include "q_field.h"
+#include "prompt.h"
+
+static cvar_t *com_completion_mode;
+static cvar_t *com_completion_treshold;
+
+static void Prompt_ShowMatches( commandPrompt_t *prompt, char **matches,
+ int start, int end )
+{
+ int count = end - start;
+ int numCols = 7, numLines;
+ int i, j, k;
+ size_t maxlen, len, total;
+ size_t colwidths[6];
+ char *match;
+
+ // determine number of columns needed
+ do {
+ numCols--;
+ numLines = ceil( ( float )count / numCols );
+ total = 0;
+ for( i = 0; i < numCols; i++ ) {
+ k = start + numLines * i;
+ if( k >= end ) {
+ break;
+ }
+ maxlen = 0;
+ for( j = k; j < k + numLines && j < end; j++ ) {
+ len = strlen( matches[j] );
+ if( maxlen < len ) {
+ maxlen = len;
+ }
+ }
+ maxlen += 2; // account for intercolumn spaces
+ if( maxlen > prompt->widthInChars ) {
+ maxlen = prompt->widthInChars;
+ }
+ colwidths[i] = maxlen;
+ total += maxlen;
+ }
+ if( total < prompt->widthInChars ) {
+ break; // this number of columns does fit
+ }
+ } while( numCols > 1 );
+
+ for( i = 0; i < numLines; i++ ) {
+ for( j = 0; j < numCols; j++ ) {
+ k = start + j * numLines + i;
+ if( k >= end ) {
+ break;
+ }
+ match = matches[k];
+ prompt->printf( "%s", match );
+ len = strlen( match );
+ if( len < colwidths[j] ) {
+ // pad with spaces
+ for( k = 0; k < colwidths[j] - len; k++ ) {
+ prompt->printf( " " );
+ }
+ }
+ }
+ prompt->printf( "\n" );
+ }
+}
+
+static void Prompt_ShowIndividualMatches(
+ commandPrompt_t *prompt,
+ char **matches,
+ int numCommands,
+ int numAliases,
+ int numCvars )
+{
+ int offset = 0;
+
+ if( numCommands ) {
+ qsort( matches + offset, numCommands, sizeof( matches[0] ), SortStrcmp );
+
+ prompt->printf( "\n%i possible command%s:\n",
+ numCommands, numCommands != 1 ? "s" : "" );
+
+ Prompt_ShowMatches( prompt, matches, offset, offset + numCommands );
+ offset += numCommands;
+ }
+
+ if( numCvars ) {
+ qsort( matches + offset, numCvars, sizeof( matches[0] ), SortStrcmp );
+
+ prompt->printf( "\n%i possible variable%s:\n",
+ numCvars, numCvars != 1 ? "s" : "" );
+
+ Prompt_ShowMatches( prompt, matches, offset, offset + numCvars );
+ offset += numCvars;
+ }
+
+ if( numAliases ) {
+ qsort( matches + offset, numAliases, sizeof( matches[0] ), SortStrcmp );
+
+ prompt->printf( "\n%i possible alias%s:\n",
+ numAliases, numAliases != 1 ? "es" : "" );
+
+ Prompt_ShowMatches( prompt, matches, offset, offset + numAliases );
+ offset += numAliases;
+ }
+}
+
+qboolean Prompt_AddMatch( genctx_t *ctx, const char *s ) {
+ int r;
+
+ if( ctx->count >= ctx->size ) {
+ return qfalse;
+ }
+ if( ctx->ignorecase ) {
+ r = Q_strncasecmp( ctx->partial, s, ctx->length );
+ } else {
+ r = strncmp( ctx->partial, s, ctx->length );
+ }
+ if( !r ) {
+ ctx->matches[ctx->count++] = Z_CopyString( s );
+ }
+ return qtrue;
+}
+
+/*
+====================
+Prompt_CompleteCommand
+====================
+*/
+void Prompt_CompleteCommand( commandPrompt_t *prompt, qboolean backslash ) {
+ inputField_t *inputLine = &prompt->inputLine;
+ char *text, *partial, *s;
+ int i, argc, currentArg, argnum;
+ size_t size, len, pos;
+ char *first, *last;
+ genctx_t ctx;
+ char *matches[MAX_MATCHES], *sortedMatches[MAX_MATCHES];
+ int numCommands = 0, numCvars = 0, numAliases = 0;
+ extern size_t cmd_string_tail;
+
+ text = inputLine->text;
+ size = inputLine->maxChars + 1;
+ pos = inputLine->cursorPos;
+
+ // prepend backslash if missing
+ if( backslash ) {
+ if( inputLine->text[0] != '\\' && inputLine->text[0] != '/' ) {
+ memmove( inputLine->text + 1, inputLine->text, size - 1 );
+ inputLine->text[0] = '\\';
+ }
+ text++;
+ size--;
+ pos--;
+ }
+
+ Cmd_TokenizeString( text, qfalse );
+
+ argc = Cmd_Argc();
+
+ // determine argument number to be completed
+ currentArg = Cmd_FindArgForOffset( pos );
+ if( currentArg == argc - 1 && cmd_string_tail ) {
+ // start completing new argument if command line has trailing whitespace
+ currentArg++;
+ }
+
+ argnum = 0;
+ s = Cmd_Argv( 0 );
+ for( i = 0; i < currentArg; i++ ) {
+ partial = Cmd_Argv( i );
+ argnum++;
+ if( *partial == ';' ) {
+ s = Cmd_Argv( i + 1 );
+ argnum = 0;
+ }
+ }
+
+ partial = Cmd_Argv( currentArg );
+ if( *partial == ';' ) {
+ currentArg++;
+ partial = Cmd_Argv( currentArg );
+ argnum = 0;
+ }
+
+ // generate matches
+ memset( &ctx, 0, sizeof( ctx ) );
+ ctx.partial = partial;
+ ctx.length = strlen( partial );
+ ctx.argnum = currentArg;
+ ctx.matches = matches;
+ ctx.size = MAX_MATCHES;
+
+ if( argnum ) {
+ Com_Generic_c( &ctx, argnum );
+ } else {
+ Cmd_Command_g( &ctx );
+ numCommands = ctx.count;
+
+ Cvar_Variable_g( &ctx );
+ numCvars = ctx.count - numCommands;
+
+ Cmd_Alias_g( &ctx );
+ numAliases = ctx.count - numCvars - numCommands;
+ }
+
+ if( !ctx.count ) {
+ pos = strlen( inputLine->text );
+ prompt->tooMany = qfalse;
+ goto finish2; // nothing found
+ }
+
+ pos = Cmd_ArgOffset( currentArg );
+ text += pos;
+ size -= pos;
+
+ // append whitespace since Cmd_TokenizeString eats it
+ if( currentArg == argc && cmd_string_tail ) {
+ *text++ = ' ';
+ pos++;
+ size--;
+ }
+
+ if( ctx.count == 1 ) {
+ // we have finished completion!
+ s = Cmd_RawArgsFrom( currentArg + 1 );
+ if( COM_HasSpaces( matches[0] ) ) {
+ pos += Q_concat( text, size, "\"", matches[0], "\" ", s, NULL );
+ } else {
+ pos += Q_concat( text, size, matches[0], " ", s, NULL );
+ }
+ pos++;
+ prompt->tooMany = qfalse;
+ goto finish1;
+ }
+
+ if( ctx.count > com_completion_treshold->integer && !prompt->tooMany ) {
+ prompt->printf( "Press TAB again to display all %d possibilities.\n", ctx.count );
+ pos = strlen( inputLine->text );
+ prompt->tooMany = qtrue;
+ goto finish1;
+ }
+
+ prompt->tooMany = qfalse;
+
+ // sort matches alphabethically
+ for( i = 0; i < ctx.count; i++ ) {
+ sortedMatches[i] = matches[i];
+ }
+ qsort( sortedMatches, ctx.count, sizeof( sortedMatches[0] ),
+ ctx.ignorecase ? SortStricmp : SortStrcmp );
+
+ // copy matching part
+ first = sortedMatches[0];
+ last = sortedMatches[ ctx.count - 1 ];
+ len = 0;
+ do {
+ if( *first != *last ) {
+ if( !ctx.ignorecase || Q_tolower( *first ) != Q_tolower( *last ) ) {
+ break;
+ }
+ }
+ text[len++] = *first;
+ if( len == size - 1 ) {
+ break;
+ }
+
+ first++;
+ last++;
+ } while( *first );
+
+ text[len] = 0;
+ pos += len;
+ size -= len;
+
+ // copy trailing arguments
+ if( currentArg + 1 < argc ) {
+ s = Cmd_RawArgsFrom( currentArg + 1 );
+ pos += Q_concat( text + len, size, " ", s, NULL );
+ }
+
+ pos++;
+
+ prompt->printf( "]\\%s\n", Cmd_ArgsFrom( 0 ) );
+ if( argnum ) {
+ goto multi;
+ }
+
+ switch( com_completion_mode->integer ) {
+ case 0:
+ // print in solid list
+ for( i = 0 ; i < ctx.count; i++ ) {
+ prompt->printf( "%s\n", sortedMatches[i] );
+ }
+ break;
+ case 1:
+ multi:
+ // print in multiple columns
+ Prompt_ShowMatches( prompt, sortedMatches, 0, ctx.count );
+ break;
+ case 2:
+ default:
+ // resort matches by type and print in multiple columns
+ Prompt_ShowIndividualMatches( prompt, matches, numCommands, numAliases, numCvars );
+ break;
+ }
+
+finish1:
+ // free matches
+ for( i = 0; i < ctx.count; i++ ) {
+ Z_Free( matches[i] );
+ }
+
+finish2:
+ // move cursor
+ if( pos >= inputLine->maxChars ) {
+ pos = inputLine->maxChars - 1;
+ }
+ inputLine->cursorPos = pos;
+}
+
+void Prompt_CompleteHistory( commandPrompt_t *prompt, qboolean forward ) {
+ char *s, *m = NULL;
+ int i, j;
+
+ if( !prompt->search ) {
+ s = prompt->inputLine.text;
+ if( *s == '/' || *s == '\\' ) {
+ s++;
+ }
+ if( !*s ) {
+ return;
+ }
+ prompt->search = Z_CopyString( s );
+ }
+
+ if( forward ) {
+ for( i = prompt->historyLineNum + 1; i < prompt->inputLineNum; i++ ) {
+ s = prompt->history[i & HISTORY_MASK];
+ if( s && strstr( s, prompt->search ) ) {
+ if( strcmp( s, prompt->inputLine.text ) ) {
+ m = s;
+ break;
+ }
+ }
+ }
+ } else {
+ j = prompt->inputLineNum - HISTORY_SIZE;
+ if( j < 0 ) {
+ j = 0;
+ }
+ for( i = prompt->historyLineNum - 1; i >= j; i-- ) {
+ s = prompt->history[i & HISTORY_MASK];
+ if( s && strstr( s, prompt->search ) ) {
+ if( strcmp( s, prompt->inputLine.text ) ) {
+ m = s;
+ break;
+ }
+ }
+ }
+ }
+
+ if( !m ) {
+ return;
+ }
+
+ prompt->historyLineNum = i;
+ IF_Replace( &prompt->inputLine, prompt->history[i & HISTORY_MASK] );
+}
+
+void Prompt_ClearState( commandPrompt_t *prompt ) {
+ prompt->tooMany = qfalse;
+ if( prompt->search ) {
+ Z_Free( prompt->search );
+ prompt->search = NULL;
+ }
+}
+
+/*
+====================
+Prompt_Action
+
+User just pressed enter
+====================
+*/
+char *Prompt_Action( commandPrompt_t *prompt ) {
+ char *s = prompt->inputLine.text;
+ int i, j;
+
+ Prompt_ClearState( prompt );
+ if( s[0] == 0 || ( ( s[0] == '/' || s[0] == '\\' ) && s[1] == 0 ) ) {
+ IF_Clear( &prompt->inputLine );
+ return NULL; // empty line
+ }
+
+ // save current line in history
+ i = prompt->inputLineNum & HISTORY_MASK;
+ j = ( prompt->inputLineNum - 1 ) & HISTORY_MASK;
+ if( !prompt->history[j] || strcmp( prompt->history[j], s ) ) {
+ if( prompt->history[i] ) {
+ Z_Free( prompt->history[i] );
+ }
+ prompt->history[i] = Z_CopyString( s );
+ prompt->inputLineNum++;
+ } else {
+ i = j;
+ }
+
+ // stop history search
+ prompt->historyLineNum = prompt->inputLineNum;
+
+ IF_Clear( &prompt->inputLine );
+
+ return prompt->history[i];
+}
+
+/*
+====================
+Prompt_HistoryUp
+====================
+*/
+void Prompt_HistoryUp( commandPrompt_t *prompt ) {
+ int i;
+
+ Prompt_ClearState( prompt );
+
+ if( prompt->historyLineNum == prompt->inputLineNum ) {
+ // save current line in history
+ i = prompt->inputLineNum & HISTORY_MASK;
+ if( prompt->history[i] ) {
+ Z_Free( prompt->history[i] );
+ }
+ prompt->history[i] = Z_CopyString( prompt->inputLine.text );
+ }
+
+ if( prompt->inputLineNum - prompt->historyLineNum < HISTORY_SIZE &&
+ prompt->historyLineNum > 0 )
+ {
+ prompt->historyLineNum--;
+ }
+
+ i = prompt->historyLineNum & HISTORY_MASK;
+ IF_Replace( &prompt->inputLine, prompt->history[i] );
+}
+
+/*
+====================
+Prompt_HistoryDown
+====================
+*/
+void Prompt_HistoryDown( commandPrompt_t *prompt ) {
+ int i;
+
+ Prompt_ClearState( prompt );
+
+ if( prompt->historyLineNum == prompt->inputLineNum ) {
+ return;
+ }
+
+ prompt->historyLineNum++;
+
+ i = prompt->historyLineNum & HISTORY_MASK;
+ IF_Replace( &prompt->inputLine, prompt->history[i] );
+}
+
+/*
+====================
+Prompt_Clear
+====================
+*/
+void Prompt_Clear( commandPrompt_t *prompt ) {
+ int i;
+
+ Prompt_ClearState( prompt );
+
+ for( i = 0; i < HISTORY_SIZE; i++ ) {
+ if( prompt->history[i] ) {
+ Z_Free( prompt->history[i] );
+ prompt->history[i] = NULL;
+ }
+ }
+
+ prompt->historyLineNum = 0;
+ prompt->inputLineNum = 0;
+
+ IF_Clear( &prompt->inputLine );
+}
+
+void Prompt_SaveHistory( commandPrompt_t *prompt, const char *filename, int lines ) {
+ qhandle_t f;
+ char *s;
+ int i;
+
+ FS_FOpenFile( filename, &f, FS_MODE_WRITE|FS_PATH_BASE );
+ if( !f ) {
+ return;
+ }
+
+ if( lines > HISTORY_SIZE ) {
+ lines = HISTORY_SIZE;
+ }
+
+ i = prompt->inputLineNum - lines;
+ if( i < 0 ) {
+ i = 0;
+ }
+ for( ; i < prompt->inputLineNum; i++ ) {
+ s = prompt->history[i & HISTORY_MASK];
+ if( s ) {
+ FS_FPrintf( f, "%s\n", s );
+ }
+ }
+
+ FS_FCloseFile( f );
+}
+
+void Prompt_LoadHistory( commandPrompt_t *prompt, const char *filename ) {
+ char buffer[MAX_FIELD_TEXT];
+ qhandle_t f;
+ int i;
+ ssize_t len;
+
+ FS_FOpenFile( filename, &f, FS_MODE_READ|FS_TYPE_REAL|FS_PATH_BASE );
+ if( !f ) {
+ return;
+ }
+
+ for( i = 0; i < HISTORY_SIZE; i++ ) {
+ if( ( len = FS_ReadLine( f, buffer, sizeof( buffer ) ) ) < 1 ) {
+ break;
+ }
+ if( prompt->history[i] ) {
+ Z_Free( prompt->history[i] );
+ }
+ prompt->history[i] = memcpy( Z_Malloc( len + 1 ), buffer, len + 1 );
+ }
+
+ FS_FCloseFile( f );
+
+ prompt->historyLineNum = i;
+ prompt->inputLineNum = i;
+}
+
+/*
+====================
+Prompt_Init
+====================
+*/
+void Prompt_Init( void ) {
+ com_completion_mode = Cvar_Get( "com_completion_mode", "1", CVAR_ARCHIVE );
+ com_completion_treshold = Cvar_Get( "com_completion_treshold", "50",
+ CVAR_ARCHIVE );
+}
+