Permalink
Browse files

First implementation of a command history

  • Loading branch information...
1 parent e20e1ce commit af352ae844937d7e05932676c54a14d04ae67abd Stephan committed Jun 8, 2012
@@ -139,5 +139,30 @@ public void PrintsExceptionsToTheConsole()
Assert.IsTrue(console.OutputQueue[2].StartsWith("Stacktrace:"));
Assert.AreEqual("Exception: Bar", console.OutputQueue[3]);
}
+
+ [Test]
+ public void ReturnsLastCommandOnArrowUp()
+ {
+ var console = new TestConsole(new List<string>() { "test", "exit", "--test" });
@cburgdorf

cburgdorf Jun 8, 2012

your UpArrow key should go somewhere here in the list. Face it: Everything you put in here is essentially the mocked keyboard input from the user!

+ var commandFactory = new CommandFactory(new[] { new TestCommand() });
+ var commandLoop = new CommandLoop(console, commandFactory);
+ commandLoop.Start(new[] { "test", "--IsTest" });
+
+ Assert.AreEqual("Run. Test: True, Text: ", console.OutputQueue[0]);
+ Assert.AreEqual("Enter commands or type exit to close", console.OutputQueue[1]);
+ Assert.AreEqual("Run. Test: False, Text: ", console.OutputQueue[2]);
+ Assert.AreEqual("Enter commands or type exit to close", console.OutputQueue[3]);
+ Assert.AreEqual(4, console.OutputQueue.Count);
+
+ commandLoop.Start(new[] { System.ConsoleKey.UpArrow.ToString() });
@cburgdorf

cburgdorf Jun 8, 2012

You are actually not testing that the UpArrow gets pressed on the console, instead, you are testing that shellme.exe will be invoked with the string representation of the UpArrow key.

+
+ Assert.AreEqual("Run. Test: True, Text: ", console.OutputQueue[0]);
+ Assert.AreEqual("Enter commands or type exit to close", console.OutputQueue[1]);
+ Assert.AreEqual("Run. Test: False, Text: ", console.OutputQueue[2]);
+ Assert.AreEqual("Enter commands or type exit to close", console.OutputQueue[3]);
+ Assert.AreEqual(4, console.OutputQueue.Count);
+
+
+ }
}
}
@@ -9,7 +9,7 @@ public class CommandLoop
private readonly CommandFactory _commandFactory;
private readonly ICommandPropertyWalker _commandPropertyWalker;
- public CommandLoop() : this(new NativeConsoleWrapper())
+ public CommandLoop() : this(new NativeConsoleWrapper(new InMemoryCommandHistory()))
@cburgdorf

cburgdorf Jun 8, 2012

Why would I want to inject the Command history into the console? If I write a WPF Console, does that mean I need to think about how to use the CommandHistory? Why would I want to do that? As a console author I want to write a console for a specific GUI technolgy but please don't force me to think about the inner workings of shell.me. Just make the history work out of the box no matter what kind of console we are dealing with!

{}
public CommandLoop(IConsole console) : this(console, new CommandFactory(new ICommand[]{}))
@@ -25,10 +25,13 @@ public CommandLoop(IConsole console, CommandFactory commandFactory, ICommandProp
Console = console;
_commandFactory = commandFactory;
_commandPropertyWalker = commandPropertyWalker;
+
}
private IConsole Console { get; set; }
+
+
public void Start(string[] args)
{
var commandMatcher = new CommandMatcher(args);
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ShellMe.CommandLine
+{
+ public interface ICommandHistory
+ {
+ void SaveCommand(string command);
+ int GetCurrentCommandIndex();
+ string GetLastCommand();
+ string GetPrevioseCommand();
+ string GetNextCommand();
+ string FindCommand(string searchstring);
+ }
+}
@@ -6,6 +6,7 @@ public interface IConsole
{
void WriteLine(string line);
string ReadLine();
+ //ConsoleKeyInfo Readkey();
ConsoleColor ForegroundColor { get; set; }
void ResetColor();
}
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ShellMe.CommandLine
+{
+ public class InMemoryCommandHistory : ICommandHistory
+ {
+ private readonly List<string> _commandHistory = new List<string>();
+ private int _currentCommandIndex = 0;
+
+ private Dictionary<ConsoleKey, Action> _matches;
+
+ public InMemoryCommandHistory()
+ {
+ _matches = new Dictionary<ConsoleKey, Action>
+ {
+ { ConsoleKey.UpArrow , () => GetPrevioseCommand() },
+ { ConsoleKey.DownArrow , () => GetNextCommand() }
+ };
+ }
+
+ public Dictionary<ConsoleKey, Action> Matches
+ {
+ get { return _matches; }
+ set { _matches = value; }
+ }
+
+ public void SaveCommand(string command)
+ {
+ _commandHistory.Add(command);
+ }
+
+ public int GetCurrentCommandIndex()
+ {
+ return _currentCommandIndex;
+ }
+
+ public string GetLastCommand()
+ {
+ return _commandHistory[_commandHistory.Count - 1];
@cburgdorf

cburgdorf Jun 8, 2012

This will fail if the command history is empty

+ }
+
+ public string GetPrevioseCommand()
+ {
+ _currentCommandIndex++;
@cburgdorf

cburgdorf Jun 8, 2012

This will fail, once we reached the end.

+ return _commandHistory[_currentCommandIndex];
+ }
+
+ public string GetNextCommand()
+ {
+ _currentCommandIndex--;
+ return _commandHistory[_currentCommandIndex];
+ }
+
+ public string FindCommand(string searchstring)
+ {
+ return _commandHistory
+ .FirstOrDefault(cmd => cmd.Contains(searchstring));
+ }
+ }
+}
@@ -1,20 +1,53 @@
using System;
+using System.Collections.Generic;
namespace ShellMe.CommandLine
{public class NativeConsoleWrapper : IConsole
{
+
+
+ public NativeConsoleWrapper()
+ : this(new InMemoryCommandHistory())
+ {}
+
+ public NativeConsoleWrapper(InMemoryCommandHistory commandHistory)
+ {
+ CommandHistory = commandHistory;
+ }
+
public void WriteLine(string line)
{
Console.WriteLine(line);
}
+
+ public InMemoryCommandHistory CommandHistory { get; set; }
+
public string ReadLine()
- {
- return Console.ReadLine();
- }
+ {
+ string buffer = string.Empty;
@cburgdorf

cburgdorf Jun 8, 2012

All this logic must stay out of the NativeConsoleWrapper. The NativeConsoleWrapper really just delegates to the System.Console. Why would I want to write this code again if I write a HtmlConsole, WpfConsole etc.

+ do
+ {
+ while (!Console.KeyAvailable)
+ {
+ var keyInfo = Console.ReadKey();
+ if (!CommandHistory.Matches.ContainsKey(keyInfo.Key))
+ {
+ buffer += keyInfo.KeyChar;
+ }
+ else
+ {
+
+ }
+ }
+ } while (Console.ReadKey(true).Key != ConsoleKey.Enter);
+
+ return buffer; // Console.ReadLine();
+ }
+
- public ConsoleColor ForegroundColor
+ public ConsoleColor ForegroundColor
{
get { return Console.ForegroundColor; }
set
@@ -52,7 +52,9 @@
<Compile Include="CommandHandling\ICommand.cs" />
<Compile Include="CommandHandling\CommandPropertyWalker.cs" />
<Compile Include="ExceptionWalker.cs" />
+ <Compile Include="ICommandHistory.cs" />
<Compile Include="IConsole.cs" />
+ <Compile Include="InMemoryCommandHistory.cs" />
<Compile Include="NativeConsoleWrapper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
@@ -11,11 +11,18 @@ public class TestConsole : IConsole
private readonly List<string> _commandQueue;
public TestConsole(List<string> commandQueue)
+ : this(new InMemoryCommandHistory(),commandQueue)
+ {}
+
+ public TestConsole(InMemoryCommandHistory commandHistory,List<string> commandQueue)
{
_commandQueue = commandQueue;
OutputQueue = new List<string>();
+ CommandHistory = commandHistory;
}
+ public InMemoryCommandHistory CommandHistory { get; set; }
+
public List<string> OutputQueue { get; private set; }
public void WriteLine(string line)
@@ -26,8 +33,27 @@ public void WriteLine(string line)
public string ReadLine()
{
var first = _commandQueue.First();
+ string buffer = string.Empty;
+ foreach (var character in first)
@cburgdorf

cburgdorf Jun 8, 2012

As I said. It makes no sense to push and duplicate this logic for all the different types of consoles. Just keep that outside.

+ {
+ ConsoleKey consoleKey;
+ Enum.TryParse(new string(new[] {character}).ToUpper(), out consoleKey);
+
+ var keyInfo = new ConsoleKeyInfo(character, consoleKey, false, false, false);
+
+ if (!CommandHistory.Matches.ContainsKey(keyInfo.Key))
+ {
+ buffer += keyInfo.KeyChar;
+ }
+ else
+ {
+ Action action = CommandHistory.Matches[keyInfo.Key];
+ action.Invoke();
+ }
+ }
+
_commandQueue.RemoveAt(0);
- return first;
+ return buffer;
}
public ConsoleColor ForegroundColor { get; set; }

1 comment on commit af352ae

Read my comments above. I really like to see this feature landing soonish!

Please sign in to comment.