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. |