unified the command history logic

when a command is the same as the previous one, it doesn't get saved
Linux tty keys support: left, right, home, end, delete
This commit is contained in:
myT 2017-06-04 13:02:20 +02:00
parent b8b064fc53
commit 8b5728559b
7 changed files with 253 additions and 188 deletions

View file

@ -280,8 +280,6 @@ static void Con_ResizeFont()
void CL_ConInit()
{
int i;
con_noprint = Cvar_Get( "con_noprint", "0", 0 );
con_notifytime = Cvar_Get( "con_notifytime", "3", CVAR_ARCHIVE );
con_scale = Cvar_Get( "con_scale", "1", CVAR_ARCHIVE );
@ -290,10 +288,7 @@ void CL_ConInit()
Field_Clear( &g_consoleField );
g_consoleField.widthInChars = g_console_field_width;
for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) {
Field_Clear( &historyEditLines[i] );
historyEditLines[i].widthInChars = g_console_field_width;
}
History_Clear( &g_history, g_console_field_width );
Con_ClearNotify();
CL_LoadCommandHistory();

View file

@ -27,23 +27,16 @@ key up events are sent even if in console mode
*/
field_t historyEditLines[COMMAND_HISTORY];
int nextHistoryLine; // the last line in the history buffer, not masked
int historyLine; // the line being displayed from history buffer
// will be <= nextHistoryLine
history_t g_history;
field_t g_consoleField;
field_t chatField;
qbool chat_team;
qbool chat_team;
int chat_playerNum;
int anykeydown;
static qbool key_overstrikeMode;
int anykeydown;
typedef struct {
qbool down;
@ -476,10 +469,7 @@ static void Console_Key( int key )
}
}
// copy line to history buffer
historyEditLines[nextHistoryLine % COMMAND_HISTORY] = g_consoleField;
nextHistoryLine++;
historyLine = nextHistoryLine;
History_SaveCommand( &g_history, &g_consoleField );
Field_Clear( &g_consoleField );
g_consoleField.widthInChars = g_console_field_width;
@ -502,24 +492,13 @@ static void Console_Key( int key )
if ( ( key == K_UPARROW ) ||
( ( tolower(key) == 'p' ) && keys[K_CTRL].down ) ) {
if ( nextHistoryLine - historyLine < COMMAND_HISTORY
&& historyLine > 0 ) {
historyLine--;
}
g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ];
History_GetPreviousCommand( &g_consoleField, &g_history );
return;
}
if ( ( key == K_DOWNARROW ) ||
( ( tolower(key) == 'n' ) && keys[K_CTRL].down ) ) {
historyLine++;
if (historyLine >= nextHistoryLine) {
historyLine = nextHistoryLine;
Field_Clear( &g_consoleField );
g_consoleField.widthInChars = g_console_field_width;
return;
}
g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ];
History_GetNextCommand( &g_consoleField, &g_history, g_console_field_width );
return;
}
@ -1177,19 +1156,19 @@ void CL_LoadCommandHistory()
for ( int i = 0; i < count; ++i ) {
const int l = lengths[i];
if ( l <= 0 ||
FS_Read( historyEditLines[i].buffer, l, f ) != l ) {
FS_Read( g_history.commands[i].buffer, l, f ) != l ) {
FS_FCloseFile( f );
return;
}
historyEditLines[i].buffer[l] = '\0';
historyEditLines[i].cursor = l;
g_history.commands[i].buffer[l] = '\0';
g_history.commands[i].cursor = l;
}
nextHistoryLine = count;
historyLine = count;
const int totalCount = ARRAY_LEN( historyEditLines );
g_history.next = count;
g_history.display = count;
const int totalCount = ARRAY_LEN( g_history.commands );
for ( int i = count; i < totalCount; ++i ) {
historyEditLines[i].buffer[0] = '\0';
g_history.commands[i].buffer[0] = '\0';
}
FS_FCloseFile(f);
@ -1207,9 +1186,9 @@ void CL_SaveCommandHistory()
int count = 0;
int lengths[COMMAND_HISTORY];
const int totalCount = ARRAY_LEN( historyEditLines );
const int totalCount = ARRAY_LEN( g_history.commands );
for ( int i = 0; i < totalCount; ++i ) {
const char* const s = historyEditLines[(historyLine + i) % COMMAND_HISTORY].buffer;
const char* const s = g_history.commands[(g_history.display + i) % COMMAND_HISTORY].buffer;
if ( *s == '\0' )
continue;
@ -1219,7 +1198,7 @@ void CL_SaveCommandHistory()
FS_Write( &count, sizeof(count), f );
FS_Write( lengths, sizeof(int) * count, f );
for ( int i = 0, j = 0; i < totalCount; ++i ) {
const char* const s = historyEditLines[(historyLine + i) % COMMAND_HISTORY].buffer;
const char* const s = g_history.commands[(g_history.display + i) % COMMAND_HISTORY].buffer;
if ( *s == '\0' )
continue;

View file

@ -24,13 +24,11 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
void Field_Draw( field_t* edit, int x, int y, int cw, int ch );
#define COMMAND_HISTORY 32
extern field_t historyEditLines[COMMAND_HISTORY];
extern field_t g_consoleField;
extern field_t chatField;
extern int anykeydown;
extern qbool chat_team;
extern history_t g_history;
extern field_t g_consoleField;
extern field_t chatField;
extern int anykeydown;
extern qbool chat_team;
extern int chat_playerNum;
void Key_WriteBindings( fileHandle_t f );

View file

@ -2929,6 +2929,75 @@ void Field_AutoCompleteKeyName( int startArg, int compArg )
#endif
void History_Clear( history_t* history, int width )
{
for ( int i = 0; i < COMMAND_HISTORY; ++i ) {
Field_Clear( &history->commands[i] );
history->commands[i].widthInChars = width;
}
}
static int LengthWithoutTrailingWhitespace( const char* s )
{
int i = (int)strlen(s);
while ( i-- ) {
if ( s[i] != ' ' && s[i] != '\t' )
return i;
}
return 0;
}
void History_SaveCommand( history_t* history, const field_t* edit )
{
// Avoid having the same command twice in a row.
// Unfortunately, this has to be case sensitive since case might matter for some commands.
if ( history->next > 0 ) {
// The real proper way to ignore whitespace is to tokenize both strings and compare the
// argument count and then each argument with a case sensitive comparison,
// but there's only one tokenizer data instance...
// Instead, we only ignore the trailing whitespace.
const int prevLine = (history->next - 1) % COMMAND_HISTORY;
const int length1 = LengthWithoutTrailingWhitespace( edit->buffer );
const int length2 = LengthWithoutTrailingWhitespace( history->commands[prevLine].buffer );
const int maxLength = min( length1, length2 );
if ( maxLength <= 0 || strncmp(edit->buffer, history->commands[prevLine].buffer, maxLength) == 0 ) {
history->display = history->next;
return;
}
}
// copy the line
history->commands[history->next % COMMAND_HISTORY] = *edit;
++history->next;
history->display = history->next;
}
void History_GetPreviousCommand( field_t* edit, history_t* history )
{
if ( history->next - history->display < COMMAND_HISTORY && history->display > 0 )
--history->display;
*edit = history->commands[history->display % COMMAND_HISTORY];
}
void History_GetNextCommand( field_t* edit, history_t* history, int width )
{
++history->display;
if ( history->display < history->next ) {
*edit = history->commands[history->display % COMMAND_HISTORY];
return;
}
history->display = history->next;
Field_Clear( edit );
edit->widthInChars = width;
}
const char* Q_itohex( uint64_t number, qbool uppercase, qbool prefix )
{

View file

@ -666,6 +666,19 @@ void Field_AutoCompleteDemoNameRead( int startArg, int compArg );
void Field_AutoCompleteDemoNameWrite( int startArg, int compArg );
void Field_AutoCompleteKeyName( int startArg, int compArg );
#define COMMAND_HISTORY 32
typedef struct {
field_t commands[COMMAND_HISTORY];
int next; // the last line in the history buffer, not masked
int display; // the line being displayed from history buffer
// will be <= nextHistoryLine
} history_t;
void History_Clear( history_t* history, int width );
void History_SaveCommand( history_t* history, const field_t* edit );
void History_GetPreviousCommand( field_t* edit, history_t* history );
void History_GetNextCommand( field_t* edit, history_t* history, int width );
/*
==============================================================

View file

@ -79,12 +79,7 @@ static field_t tty_con;
static cvar_t *ttycon_ansicolor = NULL;
static qboolean ttycon_color_on = qfalse;
// history
// NOTE TTimo this is a bit duplicate of the graphical console history
// but it's safer and faster to write our own here
#define TTY_HISTORY 32
static field_t ttyEditLines[TTY_HISTORY];
static int hist_current = -1, hist_count = 0;
static history_t tty_history;
@ -110,27 +105,33 @@ static void tty_Back()
{
char key;
key = '\b';
write(1, &key, 1);
write(STDOUT_FILENO, &key, 1);
key = ' ';
write(1, &key, 1);
write(STDOUT_FILENO, &key, 1);
key = '\b';
write(1, &key, 1);
write(STDOUT_FILENO, &key, 1);
}
// clear the display of the line currently edited
// bring cursor back to beginning of line
static void tty_Hide()
{
int i;
assert(ttycon_on);
if (ttycon_hide)
{
ttycon_hide++;
return;
}
if (tty_con.cursor>0)
const int length = strlen(tty_con.buffer);
if (length > 0)
{
for (i=0; i<tty_con.cursor; i++)
const int stepsForward = length - tty_con.cursor;
// a single write call for this didn't work on my terminal
for (int i = 0; i < stepsForward; ++i)
{
write(STDOUT_FILENO, "\033[1C", 4);
}
for (int i = 0; i < length; ++i)
{
tty_Back();
}
@ -143,76 +144,26 @@ static void tty_Hide()
// FIXME TTimo need to position the cursor if needed??
static void tty_Show()
{
int i;
assert(ttycon_on);
assert(ttycon_hide>0);
ttycon_hide--;
if (ttycon_hide == 0)
{
write(STDOUT_FILENO, "]", 1);
if (tty_con.cursor)
const int length = strlen(tty_con.buffer);
if (length > 0)
{
for (i=0; i<tty_con.cursor; i++)
const int stepsBack = length - tty_con.cursor;
write(STDOUT_FILENO, tty_con.buffer, length);
// a single write call for this didn't work on my terminal
for (int i = 0; i < stepsBack; ++i)
{
write(1, tty_con.buffer+i, 1);
}
write(STDOUT_FILENO, "\033[1D", 4);
}
}
}
}
static void tty_Hist_Add(field_t *field)
{
int i;
assert(hist_count <= TTY_HISTORY);
assert(hist_count >= 0);
assert(hist_current >= -1);
assert(hist_current <= hist_count);
// make some room
for (i=TTY_HISTORY-1; i>0; i--)
{
ttyEditLines[i] = ttyEditLines[i-1];
}
ttyEditLines[0] = *field;
if (hist_count<TTY_HISTORY)
{
hist_count++;
}
hist_current = -1; // re-init
}
static field_t* tty_Hist_Prev()
{
int hist_prev;
assert(hist_count <= TTY_HISTORY);
assert(hist_count >= 0);
assert(hist_current >= -1);
assert(hist_current <= hist_count);
hist_prev = hist_current + 1;
if (hist_prev >= hist_count)
{
return NULL;
}
hist_current++;
return &(ttyEditLines[hist_current]);
}
static field_t* tty_Hist_Next()
{
assert(hist_count <= TTY_HISTORY);
assert(hist_count >= 0);
assert(hist_current >= -1);
assert(hist_current <= hist_count);
if (hist_current >= 0)
{
hist_current--;
}
if (hist_current == -1)
{
return NULL;
}
return &(ttyEditLines[hist_current]);
}
// initialize the console input (tty mode if wanted and possible)
static void Sys_ConsoleInputInit()
@ -281,7 +232,7 @@ const char* Sys_ConsoleInput()
static char text[256];
int avail;
char key;
field_t *history;
field_t field;
if (ttycon && ttycon->value)
{
@ -289,50 +240,68 @@ const char* Sys_ConsoleInput()
if (avail != -1)
{
// we have something
// backspace?
// NOTE TTimo testing a lot of values .. seems it's the only way to get it to work everywhere
if ((key == tty_erase) || (key == 127) || (key == 8))
if (key == tty_erase || key == 127 || key == 8) // backspace
{
if (tty_con.cursor > 0)
const int length = strlen(tty_con.buffer);
if (length > 0)
{
tty_con.cursor--;
tty_con.buffer[tty_con.cursor] = '\0';
tty_Back();
if (tty_con.cursor == length)
{
tty_con.buffer[length - 1] = '\0';
tty_con.cursor--;
tty_Back();
}
else if (tty_con.cursor > 0)
{
tty_Hide();
const int toMove = length + 1 - tty_con.cursor; // with the null terminator
memmove(tty_con.buffer + tty_con.cursor - 1, tty_con.buffer + tty_con.cursor, toMove);
tty_con.cursor--;
tty_Show();
}
}
return NULL;
}
// check if this is a control char
if ((key) && (key) < ' ')
{
if (key == '\n')
{
#ifdef DEDICATED
// push it in history
tty_Hist_Add(&tty_con);
History_SaveCommand(&tty_history, &tty_con);
Q_strncpyz(text, tty_con.buffer, sizeof(text));
Field_Clear(&tty_con);
write(STDOUT_FILENO, "\n]", 2);
#else
// not in a game yet and no leading slash?
if (cls.state != CA_ACTIVE && tty_con.cursor > 0 &&
if (cls.state != CA_ACTIVE &&
tty_con.buffer[0] != '/' && tty_con.buffer[0] != '\\')
{
// there's no one to chat with, so we consider this a command
memmove(tty_con.buffer + 1, tty_con.buffer, strlen(tty_con.buffer) + 1);
tty_con.buffer[0] = '\\';
const int length = strlen(tty_con.buffer);
if (length > 0)
{
memmove(tty_con.buffer + 1, tty_con.buffer, length + 1);
}
else
{
tty_con.buffer[1] = '\0';
}
tty_con.buffer[0] = '\\';
tty_con.cursor++;
}
// decide what the final command will be
const int length = strlen(tty_con.buffer);
if (tty_con.buffer[0] == '/' || tty_con.buffer[0] == '\\')
Q_strncpyz(text, tty_con.buffer + 1, sizeof(text));
else if (tty_con.cursor)
else if (length > 0)
Com_sprintf(text, sizeof(text), "say %s", tty_con.buffer);
else
*text = '\0';
// push it in history
tty_Hist_Add(&tty_con);
History_SaveCommand(&tty_history, &tty_con);
tty_Hide();
Com_Printf("tty]%s\n", tty_con.buffer);
Field_Clear(&tty_con);
@ -359,27 +328,28 @@ const char* Sys_ConsoleInput()
{
avail = read(0, &key, 1);
if (avail != -1)
{
{
switch (key)
{
case 'A':
history = tty_Hist_Prev();
if (history)
case 'A': // up arrow (move cursor up)
History_GetPreviousCommand(&field, &tty_history);
if (field.buffer[0] != '\0')
{
tty_Hide();
tty_con = *history;
tty_con = field;
tty_Show();
}
tty_FlushIn();
return NULL;
break;
case 'B':
history = tty_Hist_Next();
case 'B': // down arrow (move cursor down)
History_GetNextCommand(&field, &tty_history, 0);
tty_Hide();
if (history)
if (tty_con.buffer[0] != '\0')
{
tty_con = *history;
} else
tty_con = field;
}
else
{
Field_Clear(&tty_con);
}
@ -387,10 +357,52 @@ const char* Sys_ConsoleInput()
tty_FlushIn();
return NULL;
break;
case 'C':
case 'C': // right arrow (move cursor right)
if (tty_con.cursor < strlen(tty_con.buffer))
{
write(STDOUT_FILENO, "\033[1C", 4);
tty_con.cursor++;
}
return NULL;
case 'D':
case 'D': // left arrow (move cursor left)
if (tty_con.cursor > 0)
{
write(STDOUT_FILENO, "\033[1D", 4);
tty_con.cursor--;
}
return NULL;
case '3': // delete (might not work on all terminals)
{
const int length = strlen(tty_con.buffer);
if (tty_con.cursor < length)
{
tty_Hide();
const int toMove = length - tty_con.cursor; // with terminating NULL
memmove(tty_con.buffer + tty_con.cursor, tty_con.buffer + tty_con.cursor + 1, toMove);
tty_Show();
}
}
tty_FlushIn();
return NULL;
case 'H': // home (move cursor to upper left corner)
if (tty_con.cursor > 0)
{
tty_Hide();
tty_con.cursor = 0;
tty_Show();
}
return NULL;
case 'F': // end (might not work on all terminals)
{
const int length = strlen(tty_con.buffer);
if (tty_con.cursor < length)
{
tty_Hide();
tty_con.cursor = length;
tty_Show();
}
}
return NULL;
}
}
}
@ -399,11 +411,25 @@ const char* Sys_ConsoleInput()
tty_FlushIn();
return NULL;
}
// push regular character
tty_con.buffer[tty_con.cursor] = key;
tty_con.cursor++;
// print the current line (this is differential)
write(1, &key, 1);
// if we get here, key is a regular character
const int length = strlen(tty_con.buffer);
if (tty_con.cursor == length)
{
write(STDOUT_FILENO, &key, 1);
tty_con.buffer[tty_con.cursor + 0] = key;
tty_con.buffer[tty_con.cursor + 1] = '\0';
tty_con.cursor++;
}
else
{
tty_Hide();
const int toMove = length + 1 - tty_con.cursor; // need to move the null terminator too
memmove(tty_con.buffer + tty_con.cursor + 1, tty_con.buffer + tty_con.cursor, toMove);
tty_con.buffer[tty_con.cursor] = key;
tty_con.cursor++;
tty_Show();
}
}
return NULL;
} else

View file

@ -75,31 +75,18 @@ typedef struct
field_t inputField;
field_t historyEditLines[COMMAND_HISTORY];
int nextHistoryLine; // the last line in the history buffer, not masked
int historyLine; // the line being displayed from history buffer
history_t history;
} WinConData;
static WinConData s_wcd;
void Con_StoreCommand( const char* inputBuffer ) {
field_t& field = s_wcd.historyEditLines[ s_wcd.nextHistoryLine % COMMAND_HISTORY ];
field_t field;
Q_strncpyz( field.buffer, inputBuffer, sizeof(field.buffer) );
field.cursor = strlen( inputBuffer );
field.scroll = 0;
field.widthInChars = field.cursor;
// avoid having the same command twice in a row
if ( s_wcd.nextHistoryLine > 0 ) {
const int prevLine = (s_wcd.nextHistoryLine - 1) % COMMAND_HISTORY;
if ( !memcmp(&field, &s_wcd.historyEditLines[prevLine], sizeof(field)) ) {
s_wcd.historyLine = s_wcd.nextHistoryLine;
return;
}
}
s_wcd.nextHistoryLine++;
s_wcd.historyLine = s_wcd.nextHistoryLine;
History_SaveCommand( &s_wcd.history, &field );
}
static LONG WINAPI ConWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
@ -298,27 +285,28 @@ LONG WINAPI InputLineWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam
break;
case WM_KEYDOWN:
if ( wParam == VK_UP ) {
if ( s_wcd.nextHistoryLine - s_wcd.historyLine < COMMAND_HISTORY && s_wcd.historyLine > 0 )
s_wcd.historyLine--;
const field_t& field = s_wcd.historyEditLines[ s_wcd.historyLine % COMMAND_HISTORY ];
if ( wParam == VK_UP )
{
field_t field;
History_GetPreviousCommand( &field, &s_wcd.history );
SetWindowText( hWnd, field.buffer );
SendMessage( hWnd, EM_SETSEL, (WPARAM)field.cursor, (LPARAM)field.cursor );
return 0;
}
if ( wParam == VK_DOWN ) {
s_wcd.historyLine++;
if ( s_wcd.historyLine >= s_wcd.nextHistoryLine ) {
s_wcd.historyLine = s_wcd.nextHistoryLine;
SetWindowText( s_wcd.hwndInputLine, "" );
return 0;
if ( wParam == VK_DOWN )
{
field_t field;
History_GetNextCommand( &field, &s_wcd.history, 0 );
if ( field.buffer[0] != '\0' )
{
SetWindowText( hWnd, field.buffer );
SendMessage( hWnd, EM_SETSEL, (WPARAM)field.cursor, (LPARAM)field.cursor );
}
else
{
SetWindowText( s_wcd.hwndInputLine, "" );
}
const field_t& field = s_wcd.historyEditLines[ s_wcd.historyLine % COMMAND_HISTORY ];
SetWindowText( hWnd, field.buffer );
SendMessage( hWnd, EM_SETSEL, (WPARAM)field.cursor, (LPARAM)field.cursor );
return 0;
}
@ -499,10 +487,7 @@ void Sys_CreateConsole( void )
SetForegroundWindow( s_wcd.hWnd );
SetFocus( s_wcd.hwndInputLine );
for ( int i = 0; i < COMMAND_HISTORY; ++i )
Field_Clear( &s_wcd.historyEditLines[i] );
s_wcd.historyLine = 0;
s_wcd.nextHistoryLine = 0;
History_Clear( &s_wcd.history, 0 );
s_wcd.visLevel = 1;
}