Skip to content

Instantly share code, notes, and snippets.

@Wunkolo
Created March 8, 2017 09:13
Show Gist options
  • Save Wunkolo/e76b397266637c8aa389f3694f43a7e7 to your computer and use it in GitHub Desktop.
Save Wunkolo/e76b397266637c8aa389f3694f43a7e7 to your computer and use it in GitHub Desktop.
#include "Console.hpp"
#include <Windows.h>
#include <conio.h> // _getch()
#pragma warning(disable:4996)
#include <io.h>
#include <cctype> //isgraph
#include <algorithm>
#define ps1beg "\xAF["
#define ps1end "]\xAE"
namespace Console
{
/// Command
class Help : public Command
{
public:
// Return true on success
bool Run(const std::vector<std::string>& Arguments) override
{
if( Arguments.size() >= 2 )
{
if( Console::Instance().Commands.count(Arguments[1]) )
{
if( Arguments.size() == 3 )
{
std::cout << Console::Instance().Commands[Arguments[1]]->Info() << std::endl;
}
else
{
std::cout << Console::Instance().Commands[Arguments[1]]->Info(Arguments.back()) << std::endl;
}
}
else
{
SetTextColor(Error);
std::cout << "Command: " << Arguments[1] << "not found." << std::endl;
}
}
else
{
// Show all command info
for( const auto& Command : Console::Instance().Commands )
{
std::string Padded(Command.first);
Padded.resize(Console::Instance().ConsoleWidth >> 1, '\xC4');
SetTextColor(Color::Info);
std::cout << Padded << std::endl;
SetTextColor(Color::Info^Color::Bright);
std::cout << Command.second->Info(Command.first) << std::endl;
}
}
return true;
}
// Command and usage info
std::string Info(const std::string& Topic = "") const override
{
return "Prints help for all commands\n"
"Type help (command name) (topic) to get help on a specific command";
}
std::string Suggest(const std::vector<std::string>& Arguments) const override
{
if( Arguments.size() == 2 )
{
if( Console::Instance().Commands.size() )
{
std::vector<std::string> Suggestions;
for( const auto& Cmd : Console::Instance().Commands )
{
if( !Arguments[1].compare(
0, Arguments[1].length(),
(Cmd.first),
0,
Arguments[1].length()
) )
{
Suggestions.push_back((Cmd.first));
}
}
if( Suggestions.size() )
{
// Return first match
return Suggestions.front();
}
}
}
return "";
}
};
class History : public Command
{
public:
// Return true on success
bool Run(const std::vector<std::string>& Arguments) override
{
SetTextColor(Color::Info);
for( auto Command : Console::Instance().PrevCommands )
{
for( const std::string& Arg : Command )
{
std::cout << Arg << ' ';
}
std::cout << std::endl;
}
return true;
}
// Command and usage info
std::string Info(const std::string& Topic = "") const override
{
return "Displays all previously entered commands";
}
};
class Quit : public Command
{
public:
// Return true on success
bool Run(const std::vector<std::string>& Arguments) override
{
char Response;
do
{
std::cout << "Are you sure you want to quit? (y/n) : ";
std::cin >> Response;
std::cout << std::endl;
Response = std::tolower(Response);
if( Response == 'y' )
{
std::exit(0);
}
else if( Response == 'n' )
{
return true;
}
} while( (Response != 'y') || (Response != 'n') );
return true;
}
// Command and usage info
std::string Info(const std::string& Topic = "") const override
{
return "Quits the application";
}
};
/// Command interface
bool Command::Run(const std::vector<std::string>& Arguments)
{
return false;
}
std::string Command::Info(const std::string & Topic) const
{
return "";
}
std::string Command::Suggest(const std::vector<std::string>& Arguments) const
{
return "";
}
/// Console
Console::Console()
:
CurArg(0)
{
ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleOutputCP(437);
CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo{};
if( GetConsoleScreenBufferInfo(ConsoleHandle, &ConsoleInfo) )
{
ConsoleWidth = ConsoleInfo.dwSize.X;
}
PushCommand("help", std::make_shared<Help>());
PushCommand("history", std::make_shared<History>());
PushCommand("quit", std::make_shared<Quit>());
PrintLine();
}
Console::~Console()
{
Commands.clear();
}
void Console::UpdateSuggestion()
{
Suggestion.clear();
if( CurArg == 0 )
{
// Find closest matching command
std::vector<std::string> Suggestions;
for( const auto& Cmd : Commands )
{
if( !CurCommand[0].compare(0, CurCommand[0].length(), (Cmd.first), 0, CurCommand[0].length()) )
{
Suggestions.push_back((Cmd.first));
}
}
if( Suggestions.size() )
{
// Return first match
Suggestion = Suggestions.front();
}
Suggestions.clear();
}
else if( CurArg > 0 )
{
// Send argument list to command for suggestion
if( Commands.count(CurCommand.front()) )
{
Suggestion = Commands[CurCommand.front()]->Suggest(CurCommand);
}
}
}
void Console::HandleInput(std::uint32_t KeyCode)
{
if( std::isgraph(KeyCode) )
{
if( CurCommand.empty() )
{
CurCommand.push_back("");
}
CurCommand.back().push_back(static_cast<char>(KeyCode));
UpdateSuggestion();
}
else if( KeyCode == ' ' ) // Space
{
if( !CurCommand.empty() )
{
if( !CurCommand.back().empty() )
{
//Go to next argument;
CurArg++;
CurCommand.push_back("");
}
UpdateSuggestion();
}
}
else if( KeyCode == '\b' ) // Backspace
{
if( !CurCommand.empty() )
{
// Remove character from current command
if( !CurCommand.back().empty() )
{
CurCommand.back().pop_back();
}
else if( CurCommand.back().empty() )
{
// Current argument is empty.
// Go back an argument
CurCommand.pop_back();
if( CurArg )
{
CurArg--;
}
}
UpdateSuggestion();
}
}
else if( KeyCode == '\r' ) // Enter
{
// Clear previous suggestion
//if( !Suggestion.empty() )
//{
// std::size_t i;
// for( i = 0; i < Suggestion.length(); i++ )
// {
// std::cout << ' ';
// }
// for( i = 0; i < Suggestion.length() + (CurCommand.size() > 1 ? 1 : 0); i++ )
// {
// std::cout << '\b';
// }
//}
if( CurCommand.back().empty() )
{
CurCommand.pop_back();
CurArg--;
}
Suggestion.clear();
PrintLine();
SetTextColor(Color::Info);
std::cout << ps1end << std::endl;
if( !CurCommand.empty() )
{
// Run command
if( Commands.count(CurCommand[0]) && Commands[CurCommand[0]] )
{
if( !Commands[CurCommand[0]]->Run(CurCommand) )
{
// Error running command;
SetTextColor(Color::Error);
std::cout << "Invalid Usage: " << CurCommand[0] << std::endl;
SetTextColor(Color::Info);
std::cout << Commands[CurCommand[0]]->Info() << std::endl;
}
}
else
{
SetTextColor(Color::Error);
std::cout << "Unknown Command: " << CurCommand[0] << std::endl;
}
}
SetTextColor(Color::Info);
if( !CurCommand.empty() )
{
PrevCommands.push_back(CurCommand);
PrevCommand = PrevCommands.end();
}
CurArg = 0;
CurCommand.clear();
}
else if( KeyCode == '\t' ) // Tab
{
// Get suggestion
if( !Suggestion.empty() && !CurCommand.empty() )
{
CurCommand.back() = Suggestion;
Suggestion.clear();
CurArg++;
CurCommand.push_back("");
UpdateSuggestion();
}
}
else if( KeyCode == 22 ) // Ctrl+v
{
// Paste in clipbard
if( OpenClipboard(nullptr) )
{
std::string Clipboard(reinterpret_cast<char*>(GetClipboardData(CF_TEXT)));
CloseClipboard();
for( const char& CurChar : Clipboard )
{
HandleInput(CurChar);
}
}
}
else if( KeyCode == 0 || KeyCode == 0xE0 ) // Escape character
{
std::uint32_t Func = _getch();
if( Func == 0x48 ) // Up
{
if( !PrevCommands.empty() )
{
if( PrevCommand != PrevCommands.begin() )
{
PrevCommand--;
}
CurCommand = *PrevCommand;
CurArg = CurCommand.size() - 1;
}
}
else if( Func == 0x50 ) // Down
{
if( !PrevCommands.empty() )
{
if( PrevCommand != PrevCommands.end() && PrevCommand != PrevCommands.end() - 1 )
{
PrevCommand++;
CurCommand = *PrevCommand;
CurArg = CurCommand.size() - 1;
}
else if( PrevCommand == PrevCommands.end() - 1 )
{
PrevCommand++;
CurArg = 0;
CurCommand.clear();
}
else
{
CurArg = 0;
CurCommand.clear();
}
}
}
else if( Func == 0x4B ) // Left
{
}
else if( Func == 0x4D ) // Right
{
}
else
{
// Unknown function key
}
}
}
void Console::PrintLine()
{
SetTextColor(Color::Info);
std::cout << '\r' << std::string(ConsoleWidth - 2, ' ') << '\r' << ps1beg;
SetTextColor(Color::Input);
for( std::size_t i = 0; i < CurCommand.size(); i++ )
{
if( i == CurArg )
{
SetTextColor(Color::Suggestion);
std::cout << Suggestion;
std::cout << std::string(Suggestion.length(), '\b');
SetTextColor(Color::Input);
std::cout << CurCommand[i];
}
else
{
std::cout << CurCommand[i] << ' ';
}
}
std::cout.flush();
}
Console& Console::Instance()
{
static Console Terminal;
return Terminal;
}
void Console::PushCommand(const std::string& CommandName, std::shared_ptr<Command> Command)
{
Commands[CommandName] = Command;
}
void Console::PopCommand(const std::string& CommandName)
{
if( Commands.count(CommandName) )
{
Commands.erase(CommandName);
}
}
void SetTextColor(std::uint8_t Color)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), Color);
}
bool AllocateConsole(const std::string& ConsoleTitle)
{
// Allocate new console window
if( !AllocConsole() )
{
MessageBox(nullptr, "Unable to allocate console", ConsoleTitle.c_str(), 0);
return false;
}
CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &ConsoleInfo);
ConsoleInfo.dwSize.Y = 25;
ConsoleInfo.dwSize.X = 30;
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), ConsoleInfo.dwSize);
if( !SetConsoleTitle(ConsoleTitle.c_str()) )
{
MessageBox(nullptr, ("Unable set console title (Error code: " + std::to_string(GetLastError()) + ')').c_str(), ConsoleTitle.c_str(), 0);
return false;
}
if( EnableMenuItem(GetSystemMenu(GetConsoleWindow(), FALSE), SC_CLOSE | SC_MINIMIZE, MF_GRAYED) == -1 )
{
MessageBox(nullptr, "Unable to enable menu item", ConsoleTitle.c_str(), 0);
return false;
}
if( !DrawMenuBar(GetConsoleWindow()) )
{
MessageBox(nullptr, ("Unable to DrawMenuBar (Error code: " + std::to_string(GetLastError()) + ')').c_str(), ConsoleTitle.c_str(), 0);
return false;
}
// Reroute std streams
//intptr_t hStd;
//int32_t hConsole;
//FILE *fp;
//// Setup std output
//hStd = reinterpret_cast<intptr_t>(GetStdHandle(STD_OUTPUT_HANDLE));
//hConsole = _open_osfhandle(hStd, _O_TEXT);
//fp = _fdopen(hConsole, "w");
//*stdout = *fp;
//setvbuf(stdout, nullptr, _IONBF, 0);
//// Setup std input
//hStd = reinterpret_cast<intptr_t>(GetStdHandle(STD_INPUT_HANDLE));
//hConsole = _open_osfhandle(hStd, _O_TEXT);
//fp = _fdopen(hConsole, "r");
//*stdin = *fp;
//setvbuf(stdin, nullptr, _IONBF, 0);
//std::ios::sync_with_stdio();
// New stream re-route method
freopen("CONIN$", "r", stdin);
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
std::wcout.clear();
std::cout.clear();
std::wcerr.clear();
std::cerr.clear();
std::wcin.clear();
std::cin.clear();
return true;
}
std::size_t GetWidth()
{
CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
if( GetConsoleScreenBufferInfo(
GetStdHandle(STD_OUTPUT_HANDLE),
&ConsoleInfo) )
{
return static_cast<size_t>(ConsoleInfo.dwSize.X);
}
return 0;
}
}
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <iostream>
namespace Console
{
class Command
{
public:
// Return true on success
virtual bool Run(const std::vector<std::string>& Arguments);
// Command and usage info
virtual std::string Info(const std::string& Topic = "") const;
// Suggest auto-complete strings for arguments
virtual std::string Suggest(const std::vector<std::string>& Arguments) const;
};
enum Color : std::uint8_t
{
None = 0,
Bright = 0x8,
Red = 0x4,
Blue = 0x1,
Green = 0x2,
Yellow = Red | Green,
Cyan = Green | Blue,
Magenta = Red | Blue,
Normal = Red | Blue | Green,
// Bitshift colors left 4 bits to get background version
BackBright = Bright << 4,
BackRed = Red << 4,
BackBlue = Blue << 4,
BackGreen = Green << 4,
BackYellow = Yellow << 4,
BackCyan = Cyan << 4,
BackMagenta = Magenta << 4,
// Prefabs
Error = Bright | Red,
Info = Bright | Yellow,
Input = Bright | Cyan,
Suggestion = Cyan
};
class Console
{
public:
Console(Console const&) = delete;
Console(Console&&) = delete;
Console& operator=(Console const&) = delete;
Console& operator=(Console&&) = delete;
void HandleInput(std::uint32_t KeyCode);
void PrintLine();
static Console& Instance();
void PushCommand(const std::string& CommandName, std::shared_ptr<Command> Command);
void PopCommand(const std::string& CommandName);
protected:
Console();
~Console();
private:
std::map<std::string, std::shared_ptr<Command>> Commands;
// History
std::vector<std::vector<std::string>> PrevCommands;
std::vector<std::vector<std::string>>::iterator PrevCommand;
// Current Command
std::size_t CurArg;
std::vector<std::string> CurCommand;
void UpdateSuggestion();
std::string Suggestion;
// Console Data
std::size_t ConsoleWidth;
void* ConsoleHandle;
// Meta commands
friend class Help;
friend class History;
friend class Quit;
};
bool AllocateConsole(const std::string& ConsoleTitle);
void SetTextColor(std::uint8_t Color);
std::size_t GetWidth();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment