Tutorial *85*
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)

There's enough new code vs old code to warrant just revealing the entire routine up front rather than trying to take it step-by-step. Replace the entire routine for this condition to:


if (key == K_TAB)

{

	// Enhanced console command completion [Fett] &ltstart>



	/*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;

	}

}

The first thing that occurs here is calls to three new functions that will count the number of possible matches for the substring that is already typed into the console. One of three conditions will exist at this point. If there are no matches, do nothing. If there are multiple matches, then call three other new functions that will list the possible matches categorized as either commands, variables or aliases. Lastly, if there is but a single match, complete the substring that has already been typed.

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;

}

This is simply a loop very similar to Cmd_CompleteCommand. Rather than return a string, however, it counts the number of matches to the substring already in the console and returns the result.

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);

}

Again, another loop. This time around, we catch each potential command, pad it with spaces for visual politeness and then add it to a string that will be printed all at once at the end of the loop. It's simple enough to take this forward and duplicate functionality for variables. This time, in CVAR.C after the function Cvar_CompleteVariable add these two functions:


/*

============

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);

}

Looks awfully familiar doesn't it? Same basic structure, just tailored a bit for the specific purpose.

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;

}

This is virtually identical to the original Cmd_CompleteCommand. Again, we just tailored it for the specific purpose. Now go ahead and add those, by now, quite familiar supporting functions:


/*

============

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);

}

We need one last minor piece to tie this altogether. In CMD.H after the line:


char 	*Cmd_CompleteCommand (char *partial);

add:


char 	*Cmd_CompleteAlias (char *partial);	// Enhanced console completion [Fett]

It's a lot of lines of code, but it's a lot of lines of repetitive code. I'm quite sure there are many more efficient ways of accomplishing the same task, but I assure you that you won't see any drops in framerate as a result of this increased functionality.

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.



 
Sign up
Login:
Passwd:
[Remember Me]