For a C# project I was lacking a console like interface, only in a Windows forms application. So I rolled my own component based on a RichTextBox. It features a history, autocompletion and command sensitive autocompletion. The goal was to make it really similar to how bash handles these things. Since I didn't want to get into threading the inputs communicated through events, and not a synchronous Console.ReadLine() method as in regular consoles.
RichTextConsole
code 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace HotKey.Graphics
{
public delegate void InputMadeEventHandler (string input);
public delegate bool SingleCommandCompletedEventHandler (string command);
public delegate void RestoreAfterSingleCommandEventHandler ();
public partial class RichTextConsole : RichTextBox
{
///
/// When the return key is pressed the current input is taken and issued to any
/// registrants of the event through the event argument.
///
public event InputMadeEventHandler OnInputMade;
///
/// When a command is fully typed out or autocompleted this event is triggered
/// so that the registrant can set a new list of commands for command sensitive
/// auto completion.
///
public event SingleCommandCompletedEventHandler OnSingleCommandCompleted;
///
/// Triggers when a command was fully typed out or autocompleted and then
/// was deleted or changed.
///
public event RestoreAfterSingleCommandEventHandler OnRestoreAfterSingleCommand;
#region Public variables
public List Commands { get; set; }
public string Prompt { get; set; }
#endregion
#region Private variables
private class internalInput
{
public string[] words;
public int index;
}
private int offset;
private List history = new List();
private int historyPos = 0;
private string curLine;
private bool isSingleCommand = false;
#endregion
public RichTextConsole()
{
InitializeComponent();
}
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
SelectionStart = TextLength;
}
protected override void OnKeyDown(KeyEventArgs e)
{
internalInput tmp = GetInput();
if (SelectionStart < offset)
{
if (!(e.Control & e.KeyCode == Keys.C)) // Enable ctrl+c'ing of marked text
{
e.Handled = true;
e.SuppressKeyPress = true;
return;
}
}
if (e.KeyCode == Keys.Up | e.KeyCode == Keys.Down)
{
e.Handled = true;
if (historyPos == history.Count && e.KeyCode == Keys.Up)
curLine = Text.Substring(offset);
if ((historyPos == history.Count && e.KeyCode == Keys.Down)
| (historyPos == 0 && e.KeyCode == Keys.Up))
return;
historyPos += e.KeyCode == Keys.Up ? -1 : 1;
if (historyPos == history.Count)
SetInput(curLine);
else
SetInput(history.ElementAt(historyPos));
}
if (e.KeyCode == Keys.Left | e.KeyCode == Keys.Back)
{
if (SelectionStart == offset) e.Handled = true;
}
if (e.KeyCode == Keys.Enter)
{
e.Handled = true;
string str = Text.Substring(offset).Trim();
if (str.Length == 0)
return;
OnInputMade(str);
history.Add(str);
historyPos = history.Count();
NewPrompt();
}
if (e.KeyCode == Keys.Tab)
{
e.SuppressKeyPress = true;
e.Handled = true;
// Do completion!
var matches = Commands.Where(c => c.StartsWith(tmp.words[tmp.index]));
switch (matches.Count())
{
case 0:
// Simple
break;
case 1:
// Insert completed command
Write(matches.First().Substring(tmp.words[tmp.index].Length));
if (SelectionStart == TextLength) Write(" ");
break;
default:
// Show matches
var orderedMatches = matches.OrderBy(m => m);
int max = orderedMatches.Max(m => m.Length) + 2;
int colcount = 80 / max; // Character width for autocomplete suggestions is hardcoded :(
string tmpstr = "";
for (int i = 0; i < orderedMatches.Count(); i++)
tmpstr += (i % colcount == 0 ? "\n" : "") + orderedMatches.ElementAt(i).PadRight(max);
Write(tmpstr);
// Detect how far all matches are equal
int x = tmp.words[tmp.index].Length;
var charArrays = matches.Select(m => m.ToCharArray());
while (
x < charArrays.Min(ca => ca.Count()) &&
charArrays.All(ca => ca[x] == charArrays.First()[x])
) x++;
// Reconstruct GetInput line
NewPrompt();
for (int i = 0; i < tmp.words.Count(); i++)
{
if (tmp.index == i)
Write(matches.First().Substring(0, x));
else
Write(tmp.words[i]);
if (i != tmp.words.Count() - 1) Write(" ");
}
SelectionStart = offset + tmp.words.Take(tmp.index).Sum(w => w.Length + 1) + x;
break;
}
}
base.OnKeyDown(e);
// See if there is an exact match for command sensitive auto completion.
tmp = GetInput();
if (tmp.words.Count() == 2 && Commands.Contains(tmp.words[0]))
isSingleCommand = OnSingleCommandCompleted(tmp.words[0]);
else
if (isSingleCommand) OnRestoreAfterSingleCommand();
}
#region Public functions
public void NewPrompt()
{
if (TextLength == 0)
Write(Prompt);
else
WriteLine(Prompt);
offset = TextLength;
}
public void Write(string text)
{
Enabled = false; // Disabling makes the caret disappear while
// updating and prevents flickering.
int tmp = SelectionStart;
Text = Text.Insert(SelectionStart, text);
SelectionStart = tmp + text.Length;
ScrollToCaret();
Enabled = true;
Focus();
}
public void WriteLine(string line)
{
Enabled = false; // Disabling makes the caret disappear while
// updating and prevents flickering.
Text += "\n" + line;
SelectionStart = TextLength;
ScrollToCaret();
Enabled = true;
Focus();
}
public void Empty()
{
Text = "";
NewPrompt();
}
#endregion
#region Private methods
private internalInput GetInput()
{
internalInput i = new internalInput();
i.words = Text.Substring(offset).Split(" ".ToCharArray());
i.index = 0;
int sum = 0;
for (int k = 0; k < i.words.Count(); k++)
{
sum += i.words[k].Length + 1;
if (sum > SelectionStart - offset)
{
i.index = k;
break;
}
}
return i;
}
private void SetInput(string s)
{
if (TextLength > offset)
Text = Text.Remove(offset);
Text += s;
SelectionStart = TextLength;
}
#endregion
}
}