This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

 

  Add Tab-Completion To Your Console
  Submitted by



This is one of those things which is either wanky or makes a game/app look professional. The idea is to complete a command for the user when they press tab, a la tcsh, bash & friends.

To do this, I store all commands and console variables in a single stl map (StateManager is the class which looks after console commands and variables, but not the drawing of the console):

        typedef void (StateManager::*action)(const string &) throw();
        typedef struct {
                action act;		// what to do when we get this command
                string help;		// help/description string for this command
        } cmd_desc;
        typedef map<string, cmd_desc cmd_map;
        typedef map<string, Variable * var_map;

cmd_map commands; var_map variables;





For variables, act points to a member method which looks up the variable name in another map and does stuff to it. The parameter to action is the complete command line.

To have the command completion, you need to find the set of possible commands and variables that start with what is already on the command line and then find the longest string that they have in common at their beginning. The following is called with the command so far when the user presses tab and returns a string that should be appended to the command line:

string StateManager::tabComplete(const string &s)
{
        cmd_map::iterator ci=commands.begin();
        vector<string possibles;		// possible matching commands
        istrstream ss(s.c_str());		// parse command line at spaces
        string cmd, arg, thisone, lcd;
        int len, nlen, plen;

ss cmd; if(ss arg) // there is more than just a command return ""; // filename completion omitted for brevity len=cmd.length(); // how much to go on while(ci != commands.end()){ thisone=ci-first.substr(0, len);

if(thisone cmd) // passed the possibles break; // due ordering of map traversal if(thisone == cmd) possibles.push_back(ci-first);

++ci; }

if(possibles.empty()) // nothing matching return "";

arg=possibles[0]; lcd=arg.substr(len, arg.length()); plen=lcd.length();

if(possibles.size() == 1) // one match, append space & return it return lcd+" ";

for(size_t i=1;i<possibles.size();i++){ nlen=matching_chars(lcd, possibles[i].substr(len, possibles[i].length())); if(nlen < plen){ plen=nlen; lcd=lcd.substr(0, plen); }

arg+=" "+possibles[i]; }

if(!lcd.length()) // nothing in common = console-writeLine(arg);// any of them are possible, print them all out return lcd; // common prefix to all possibilities }

// yes, this is probably reinventing the wheel int matching_chars(const string &a, const string &b) throw() { int ml=min(a.length(), b.length());

for(int i=0;i<ml;i++) if(a[i] != b[i]) return i;

return ml; }



If you want this code, take the algorithm. The code is from my engine which is under the LGPL so you might not want to just copy & paste.

If you want it a little simpler, be like Quake and just fill in the first command that matches. But I find that much more annoying than the tcsh-style behaviour - it always fills in something you don't expect.

William Brodie-Tyrrell

The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

 

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.