So you want to make a Command Line Interface
posted Aug 26th 2010 12:29pm by Jakob Griffithfiled under: how-to

[Keba] not only asked Answeres.HackaDay.com, but also sent us an email as follows.
“Can you make a basic guide to designing a good Command Line User Interface?”
Wouldn’t you know the luck, I’m currently working on a Command Line type interface for a project of mine. While after the jump I’ll be walking through my explanation, it should be noted that the other replies to Answers.HackaDay.com are also great suggestions.
We have no real idea how [Keba] intends to implement a system for the ATmega16 (Serial display? Output to an LCD? etc?), but for my project it is as follows. Using C# along with DirectX (can you tell I’m making a game with a developer console?) I’ll display an input line, suggestions for inputs (intellisense), and outputs based only when a correct input is given.
To begin, and to stay focused on only the CLI, I’ll assume your project has all the necessary startup and load functions. In my case, loading of a DX device, and input handling. Also, we assume you know how to program in your respective language.
I’ll be using a pretty advanced technique (StringBuilder) for string handling, because traditional string + string concatenation is terrible on memory (and games need as much as they can get). If you don’t care for memory, you can simply use regular strings.
To start off we’ll need some global variables,
public bool bool_isConsoleOpen = false; //console, also known as CLI public StringBuilder StringBuilder_Console = new StringBuilder(); //could be replaced with string public InputDevice ID = new InputDevice();
Within the main function loop, make a call to a method named UpdateConsole();
Now, in my setup to prevent unwanted user input there is a small check to see if the console is ‘open’ or ‘closed’.
public void UpdateConsole()
{
//opening console
if (ID.isKeyDown(Keys.Oemtilde) && ID.isOldKeyUp(Keys.Oemtilde))
if (bool_isConsoleOpen == false)
{
bool_isConsoleOpen = true; //user pressed magic key, open console
StringBuilder_Console = new StringBuilder(); //clear string
}
else
bool_isConsoleOpen = false; //user pressed magic key, close console
}
The next section of code handles all the inputs (keyboard presses) and builds our string that is about to be entered. It includes support for shift capitals, pasting from the clipboard, and also checks to make sure each key entered is allowed. Simply add this portion immediately after bool_isConsoleOpen = false;.
//appending console if its open.
if (bool_isConsoleOpen == true)
{
bool caps = false; //variable that helps determine if shift is pressed
if (ID.isKeyDown(Keys.ShiftKey))
caps = true;
List pressedkeystemp = ID.PressedKeys; //I had to modify my ID a bit to make it get a list/array of the keys pressed.
//go through each new key in list
foreach (Keys currentkey in pressedkeystemp)
{
//make a string, this is for numbers
string key;
//if the key SPACE is pressed, make a space
if (currentkey == Keys.Space)
{
StringBuilder_Console.Append(" ");
}
//if the key BACK is pressed, backspace
else if (currentkey == Keys.Back)
{
if (StringBuilder_Console.Length > 0)
StringBuilder_Console.Remove(StringBuilder_Console.Length - 1, 1);
}
//if enter is pressed
else if (currentkey == Keys.Enter)
{
//send it off to apply our data
ApplicationSettings(StringBuilder_Console.ToString());
//clear our string
StringBuilder_Console = new StringBuilder();
}
//if a number is pressed, make it show up
else if (StringKeyINTCheck(currentkey, out key))
{
StringBuilder_Console.Append(key);
}
//if a-z is pressed, make it show up
else if (StringKeyCheck(currentkey))
{
// if V was just pressed and either control key is down
if (currentkey == Keys.V && (ID.isKeyDown(Keys.ControlKey)))
{
// paste time!
string pastevalue = "";
pastevalue = System.Windows.Forms.Clipboard.GetText(System.Windows.Forms.TextDataFormat.Text);
StringBuilder_Console.Append(pastevalue);
}
// if not pasting, do a regular key
else if (!caps)
StringBuilder_Console.Append(currentkey.ToString().ToLower());
else if (caps)
StringBuilder_Console.Append(currentkey.ToString());
}
}
In order to prevent some characters from being printed, such as alt characters, and to make sure the input key can actually be displayed (otherwise you could crash with error) I implement a few checks. You’ll notice I have two different types, Check(input, output) and Check(input). The former is necessary because often the input is the ASCII value, and needs to be converted to a char or string before being added to the builder. The latter simply returns true or false if the key is valid.
Example of the first, numerals
//numerals
private bool StringKeyINTCheck(Keys key, out string i)
{
if (key == Keys.D1 || key == Keys.NumPad1)
{
i = "1";
return true;
}
else if (key == Keys.D2 || key == Keys.NumPad2)
{
i = "2";
return true;
}
etc...
}
And the latter, a-z
private bool StringKeyCheck(Keys key)
{
if (key == Keys.A ||
key == Keys.B ||
key == Keys.C ||
etc...
key == Keys.X ||
key == Keys.Y ||
key == Keys.Z)
return true;
else
return false;
}
So now we have our string built, you’ll notice the new method ApplicationSettings(string) is called whenever enter is pressed. This is the sending off of the string the user just typed in/that we built, we must now break that string down and determine what the user typed, and what should happen.
Once again, I start off with a few checks, just to prevent crashes.
private void ApplicationSettings(string temp)
{
if (temp != null) //make sure the user didn't type in "".
{
//make it all lower case
temp = temp.ToLower();
//split by spaces
string[] words = temp.Split(' ');
}
}
Now comes the fun part, We’ve assumed the user has entered things such as “quit” “fullscreen 1″ and “pos 100x100x100″. The first will quit the application, the second will determine if the application should be fullscreen or not. And the final sets the users XYZ position in space. These three are simply examples of multiple variable entry, and you could of course program whatever you need.
Immediately after string[] words = temp.Split(‘ ‘); add the following,
try
{
//quit exit
if (words[0] == "quit" || words[0] == "exit")
this.Close();
//check for users fullscreen preference
else if (words[0] == "fullscreen")
{
if (words[1] == "0")
WindowedMode = true; //arbitrary global named windowedMode
else if (words[1] == "1")
WindowedMode = false;
}
//set the camera position
else if (words[0] == "pos")
{
if (words[1].Contains("x"))
{
string[] res = words[1].Split('x');
int int_x = Convert.ToInt32(res[0]);
int int_y = Convert.ToInt32(res[1]);
int int_z = Convert.ToInt32(res[2]);
Cam.Position = new Vector3(int_x, int_y, int_z);//arbitrary class camera Cam
}
}
}
catch (IndexOutOfRangeException e)
{
//this occurs when the user types "fullscreen $". Where $ is a variable, and the user typed nothing.
//do nothing we should tell the user this with an error message.
}
catch (FormatException e)
{
//this occurs when the user types "resolution $x$", where $ is an int variable, and the user typed alpha.
//do nothing we should tell the user this with an error message.
}
You probably could stop here if needed, you have input and output. However, I have something like 40 different commands in the current revision of my console, I couldn’t remember them all. So I made my own nifty intellisense.
This is going to require setting up another global–string list, filling it with commands, and then alphabetizing it.
List<string> ListString_Console = new List<string>();
private void LoadConsoleWordList()
{
ListString_Console.Clear();
//load in our console!
ListString_Console.Add("fullscreen");
ListString_Console.Add("resolution");
ListString_Console.Add("showfps");
//ListString_Console.Add("vertsync");
ListString_Console.Add("maxfps");
ListString_Console.Add("quit");
ListString_Console.Add("exit");
ListString_Console.Add("saveconsole");
//ListString_Console.Add("bind");
etc...
//sort our list
ListString_Console.Sort();
}
Now at the bottom of our UpdateConsole().
if (bool_isConsoleOpen == true)
{
BMF_Arial.AddString(StringBuilder_Console.ToString() + "_", "console", new System.Drawing.RectangleF(5, 18, Resolution.Width, 20)); //how I draw things to the screen in DX. StringBuilder_Console is the string we built earlier, so the user can see what he is typing.
//help our user search.
int q = 35;
//check every single string we know against what the user is typing in
foreach (string stringy in ListString_Console)
{
//so long as the length is right, we continue
if (stringy.Length >= StringBuilder_Console.Length) //this part could be eliminated, and we could simply go through every letter. But this speeds up operations a smidge.
{
//temporary bool
bool hodling = false;
//go through every letter
for (int i = 0; i < StringBuilder_Console.Length; i++)
if (stringy[i] == StringBuilder_Console[i])
hodling = true;
else
{
hodling = false;
break;
}
//if it's a 100% match
if (hodling)
{
//draw it, and update q relative.
BMF_Arial.AddString(stringy, "console", new RectangleF(5, 2 + q, Resolution.Width, 20)); //these are all the matches to the currently types string.
q += 18;
}
}
}
}
So how does it finally look?
No console open,
Hitting the magical key opens up console, begin typing, see intellisense,
Continue typing, other words that don’t match get taken off display,
and hitting enter executes the command,












Man! I’ve been looking for something like this for a very long time!