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