|
I saw Shelob's tutorial on adding Tab Completion of Alias Commands to NetQuake and it was the first time
in over two years I had even thought of the tab completion feature. I always hated it because it never seemed
to return the command I wanted (personal problem). While I don't think anyone has forwarded the Quake Source
far enough for it to be able to determine what you are thinking (yet), I found it fairly simple to enhance
this feature to make it a bit more useful. I have always been fond of the feature found in most real command shells (see *nix, not DOS) where the tab key would attempt to autocomplete your current line for you. The key selling point was always that in lieu of being able to determine an exact match, it would grant you a list of all the 'potential completions' so you could gradually feed it a few more keystrokes, eventually narrowing it down far enough that the shell could eventually autocomplete for you (hopefully before you reached the last letter of the command you were grasping for). This was especially helpful when grappling with mixed-cases in a command (see *nix, not DOS). While we won't have any case-sensitivity problems in the console, this is still a nifty feature to have. As-is this tutorial is designed for NetQuake but should be easily ported to QuakeWorld.
Let's start with the only function that needs to be altered to complete our mission. In the KEYS.C function
Keys_Console find the second conditional that start with the line: if (key == K_TAB)
if (key == K_TAB)
{
// Enhanced console command completion [Fett] <start>
/*IDCODE
cmd = Cmd_CompleteCommand (key_lines[edit_line]+1);
if (!cmd)
cmd = Cvar_CompleteVariable (key_lines[edit_line]+1);
*/
int c, v, a;
// Count the number of possible matches
c = Cmd_CompleteCountPossible (key_lines[edit_line]+1);
v = Cvar_CompleteCountPossible (key_lines[edit_line]+1);
a = Cmd_CompleteAliasCountPossible (key_lines[edit_line]+1);
if (!(c + v + a)) // No possible matches, don't do anything
return;
if (c + v > 1) // More than a single possible match
{
// the 'classic' Quakebar
Con_Printf("\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n");
// Print possible commands
if (c)
{
if (c==1)
Con_Printf("1 possible command:\n");
else
Con_Printf("%i possible commands:\n", c);
Cmd_CompletePrintPossible (key_lines[edit_line]+1);
}
// Print possible variables
if (v)
{
if (v==1)
Con_Printf("1 possible variable:\n");
else
Con_Printf("%i possible variables:\n", v);
Cvar_CompletePrintPossible (key_lines[edit_line]+1);
}
// Print possible aliases
if (a)
{
if (a==1)
Con_Printf("1 possible alias:\n");
else
Con_Printf("%i possible aliases:\n", a);
Cmd_CompleteAliasPrintPossible (key_lines[edit_line]+1);
}
return;
}
// We know there's only one match so use the original id functions
// to complete the line.
if (c)
cmd = Cmd_CompleteCommand (key_lines[edit_line]+1);
if (v)
cmd = Cvar_CompleteVariable (key_lines[edit_line]+1);
if (a)
cmd = Cmd_CompleteAlias (key_lines[edit_line]+1);
// Enhanced console command completion [Fett] <end>
if(cmd)
{
Q_strcpy (key_lines[edit_line]+1, cmd);
key_linepos = Q_strlen(cmd)+1;
key_lines[edit_line][key_linepos] = ' ';
key_linepos++;
key_lines[edit_line][key_linepos] = 0;
return;
}
}
Let's take a look at the two new functions that will handle the counting and listing of the commands. The functions that handle variables and aliases are merely slightly modified clones so once commands are understood, they easily follow:
in CMD.C after the function Cmd_CompleteCommand add:
/*
============
Cmd_CompleteCountPossible
New function for Enhanced console completion [Fett]
============
*/
int Cmd_CompleteCountPossible (char *partial)
{
cmd_function_t *cmd;
int len;
int h;
h=0;
len = Q_strlen(partial);
if (!len)
return 0;
// Loop through the command list and count all partial matches
for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
if (!Q_strncmp (partial,cmd->name, len))
h++;
return h;
}
After this new function add:
/*
============
Cmd_CompletePrintPossible
New function for Enhanced console completion [Fett]
============
*/
void Cmd_CompletePrintPossible (char *partial)
{
cmd_function_t *cmd;
int len;
int lpos;
int out;
int con_linewidth;
char sout[25];
char lout[1024];
lpos = 0;
len = Q_strlen(partial);
Q_strcpy(lout,"");
// Determine the width of the console - 1
con_linewidth = (vid.width >> 3) - 3;
// Loop through the command list and print all matches
for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
if (!Q_strncmp (partial,cmd->name, len))
{
Q_strcpy(sout, cmd->name);
out = Q_strlen(sout);
lpos += out;
// Pad with spaces
for (out; out<20; out++)
{
if (lpos < con_linewidth)
Q_strcat (sout, " ");
lpos++;
}
Q_strcat (lout, sout);
if (lpos > con_linewidth - 24)
for (lpos; lpos < con_linewidth; lpos++)
Q_strcat(lout, " ");
if (lpos >= con_linewidth)
lpos = 0;
}
Con_Printf ("%s\n\n", lout);
}
/*
============
Cvar_CompleteCountPossible
New function for Enhanced console completion [Fett]
============
*/
int Cvar_CompleteCountPossible (char *partial)
{
cvar_t *cvar;
int len;
int h;
h=0;
len = Q_strlen(partial);
if (!len)
return 0;
// Loop through the cvars and count all partial matches
for (cvar=cvar_vars ; cvar ; cvar=cvar->next)
if (!Q_strncmp (partial,cvar->name, len))
h++;
return h;
}
/*
============
Cvar_CompletePrintPossible
New function for Enhanced console completion [Fett]
============
*/
void Cvar_CompletePrintPossible (char *partial)
{
cvar_t *cvar;
int len;
int lpos;
int out;
int con_linewidth;
char sout[25];
char lout[1024];
len = Q_strlen(partial);
lpos = 0;
Q_strcpy(lout,"");
// Determine the width of the console
con_linewidth = (vid.width >> 3) - 3;
// Loop through the cvars and print all matches
for (cvar=cvar_vars ; cvar ; cvar=cvar->next)
if (!Q_strncmp (partial,cvar->name, len))
{
Q_strcpy(sout, cvar->name);
out = Q_strlen(sout);
lpos += out;
// Pad with spaces
for (out; out<20; out++)
{
if (lpos < con_linewidth)
Q_strcat (sout, " ");
lpos++;
}
Q_strcat (lout, sout);
if (lpos > con_linewidth - 24)
for (lpos; lpos < con_linewidth; lpos++)
Q_strcat(lout, " ");
if (lpos >= con_linewidth)
lpos = 0;
}
Con_Printf ("%s\n\n", lout);
}
By now, I'm sure you've figured out that we're going to do the same thing for aliases. One thing we have to take care of first. NetQuake, unlike QuakeWorld, doesn't have support to autocomplete aliases. We'll need to add this functionality first.
Back in CMD.C immediately after our new function Cmd_CompletePrintPossible add:
/*
============
Cmd_CompleteAlias
New function for Enhanced console completion [Fett]
============
*/
char *Cmd_CompleteAlias (char *partial)
{
cmdalias_t *alias;
int len;
len = Q_strlen(partial);
if (!len)
return NULL;
// check functions
for (alias=cmd_alias ; alias ; alias=alias->next)
if (!Q_strncmp (partial,alias->name, len))
return alias->name;
return NULL;
}
/*
============
Cmd_CompleteAliasCountPossible
New function for Enhanced console completion [Fett]
============
*/
int Cmd_CompleteAliasCountPossible (char *partial)
{
cmdalias_t *alias;
int len;
int h;
h=0;
len = Q_strlen(partial);
if (!len)
return 0;
// Loop through the command list and count all partial matches
for (alias=cmd_alias ; alias ; alias=alias->next)
if (!Q_strncmp (partial,alias->name, len))
h++;
return h;
}
/*
============
Cmd_CompleteAliasPrintPossible
New function for Enhanced console completion [Fett]
============
*/
void Cmd_CompleteAliasPrintPossible (char *partial)
{
cmdalias_t *alias;
int len;
int lpos;
int out;
int con_linewidth;
char sout[25];
char lout[1024];
lpos = 0;
len = Q_strlen(partial);
Q_strcpy(lout,"");
// Determine the width of the console -1
con_linewidth = (vid.width >> 3) - 3;
// Loop through the alias list and print all matches
for (alias=cmd_alias ; alias ; alias=alias->next)
if (!Q_strncmp (partial,alias->name, len))
{
Q_strcpy(sout, alias->name);
out = Q_strlen(sout);
lpos += out;
// Pad with spaces
for (out; out<20; out++)
{
if (lpos < con_linewidth)
Q_strcat (sout, " ");
lpos++;
}
Q_strcat (lout, sout);
if (lpos > con_linewidth - 24)
for (lpos; lpos < con_linewidth; lpos++)
Q_strcat(lout, " ");
if (lpos >= con_linewidth)
lpos = 0;
}
Con_Printf ("%s\n\n", lout);
}
char *Cmd_CompleteCommand (char *partial); char *Cmd_CompleteAlias (char *partial); // Enhanced console completion [Fett] There are two potential monkey wrenches that I can see offhand that can be thrown into the works here. The output of the lists are formatted to take into account that the longest possible returned command is 23 characters long (vid_describecurrentmode, yuck). If a user alias is created that is more than 23 characters it'll cause the whole thing to blow chunks since the temporary string 'sout' will get overloaded with characters. You might want to investigate the possibility of limiting the length of the names of user created aliases to prevent this unlikely occurence (and keep a reasonable sized leash on any post-QSource release created commands and console variables as well). Second, only 1024 bytes of memory are allocated to the output string 'lout'. A lot of aliases all beginning with the same common letters could cause this to become overloaded as well (this is a generous amount of memory. It'll need to try to print more than 40 commands, 40 variables OR 40 aliases at the same time to spew). Increase the buffer size, or add some sanity checks to keep an eye on user aliases, or both... your choice. As usual, this code 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. While the GPL doesn't require that you give me credit if you use my code, it would be a nice thing to do. |