Created
March 8, 2017 09:13
-
-
Save Wunkolo/e76b397266637c8aa389f3694f43a7e7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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