The console is now awesome on Windows too.

develop
Petr Mrázek 2011-07-15 19:58:17 +02:00
parent 459d48d75a
commit b85f196dc4
2 changed files with 366 additions and 267 deletions

@ -1,30 +1,11 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
A thread-safe logging console with a line editor.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
/*
Parts of this code are based on linenoise:
linenoise.c -- guerrilla line editing library against the idea that a
Based on linenoise:
linenoise -- guerrilla line editing library against the idea that a
line editing lib needs to be 20,000 lines of C code.
You can find the latest source code at:
@ -144,11 +125,6 @@ namespace DFHack
class Private
{
public:
enum console_state
{
con_unclaimed,
con_lineedit
};
Private()
{
dfout_C = NULL;
@ -199,6 +175,7 @@ namespace DFHack
color(Console::COLOR_LIGHTRED);
int ret = vfprintf( dfout_C, format, vl );
reset_color();
return ret;
}
}
/// Print a formatted string, like printf, in red
@ -592,7 +569,12 @@ namespace DFHack
// state variables
bool rawmode; // is raw mode active?
termios orig_termios; // saved/restored by raw mode
int state; // current state
// current state
enum console_state
{
con_unclaimed,
con_lineedit
} state;
std::string prompt; // current prompt string
std::string raw_buffer; // current raw mode buffer
int raw_cursor; // cursor position in the buffer
@ -698,12 +680,6 @@ void Console::cursor(bool enable)
SDL_mutexV(d->wlock);
}
void Console::msleep (unsigned int msec)
{
if (msec > 1000) sleep(msec/1000000);
usleep((msec % 1000000) * 1000);
}
// push to front, remove from back if we are above maximum. ignore immediate duplicates
void Console::history_add(const std::string & command)
{
@ -719,3 +695,9 @@ int Console::lineedit(const std::string & prompt, std::string & output)
SDL_mutexV(d->wlock);
return ret;
}
void Console::msleep (unsigned int msec)
{
if (msec > 1000) sleep(msec/1000000);
usleep((msec % 1000000) * 1000);
}

@ -1,33 +1,11 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
A thread-safe logging console with a line editor for windows.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
/*
Some functions are based on the linenoise win32 port:
linenoise_win32.c -- Linenoise win32 port.
Modifications copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
Based on linenoise win32 port,
copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
All rights reserved.
Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
The original linenoise can be found at: http://github.com/antirez/linenoise
@ -74,6 +52,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include <string>
#include "dfhack/Console.h"
#include "dfhack/FakeSDL.h"
#include <cstdio>
#include <cstdlib>
#include <sstream>
@ -97,138 +76,104 @@ namespace DFHack
console_out = 0;
ConsoleWindow = 0;
default_attributes = 0;
state = con_unclaimed;
raw_cursor = 0;
};
void output(const char* str, size_t len, int x, int y)
/// Print a formatted string, like printf
int print(const char * format, ...)
{
COORD pos = { (SHORT)x, (SHORT)y };
DWORD count = 0;
WriteConsoleOutputCharacterA(console_out, str, len, pos, &count);
}
FILE * dfout_C;
duthomhas::stdiobuf * stream_o;
int rawmode; /* for atexit() function to check if restore is needed*/
std::deque <std::string> history;
HANDLE console_in;
HANDLE console_out;
HWND ConsoleWindow;
WORD default_attributes;
};
va_list args;
va_start( args, format );
int ret = vprint( format, args );
va_end( args );
return ret;
}
Console::Console():std::ostream(0), std::ios(0)
int vprint(const char * format, va_list vl)
{
d = 0;
}
Console::~Console()
if(state == con_lineedit)
{
clearline();
int ret = vfprintf( dfout_C, format, vl );
prompt_refresh();
return ret;
}
bool Console::init(void)
else return vfprintf( dfout_C, format, vl );
}
int vprinterr(const char * format, va_list vl)
{
d = new Private();
int hConHandle;
long lStdHandle;
CONSOLE_SCREEN_BUFFER_INFO coninfo;
FILE *fp;
DWORD oldMode, newMode;
// Allocate a console!
AllocConsole();
d->ConsoleWindow = GetConsoleWindow();
HMENU hm = GetSystemMenu(d->ConsoleWindow,false);
DeleteMenu(hm, SC_CLOSE, MF_BYCOMMAND);
// set the screen buffer to be big enough to let us scroll text
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
d->default_attributes = coninfo.wAttributes;
coninfo.dwSize.Y = MAX_CONSOLE_LINES; // How many lines do you want to have in the console buffer
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
// redirect unbuffered STDOUT to the console
d->console_out = GetStdHandle(STD_OUTPUT_HANDLE);
lStdHandle = (long)d->console_out;
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
d->dfout_C = _fdopen( hConHandle, "w" );
setvbuf( d->dfout_C, NULL, _IONBF, 0 );
// redirect unbuffered STDIN to the console
d->console_in = GetStdHandle(STD_INPUT_HANDLE);
lStdHandle = (long)d->console_in;
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
fp = _fdopen( hConHandle, "r" );
*stdin = *fp;
setvbuf( stdin, NULL, _IONBF, 0 );
GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),&oldMode);
newMode = oldMode | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),newMode);
std::ios::sync_with_stdio();
// make our own weird streams so our IO isn't redirected
d->stream_o = new duthomhas::stdiobuf(d->dfout_C);
rdbuf(d->stream_o);
std::cin.tie(this);
clear();
return true;
if(state == con_lineedit)
{
color(Console::COLOR_LIGHTRED);
clearline();
int ret = vfprintf( dfout_C, format, vl );
reset_color();
prompt_refresh();
return ret;
}
bool Console::shutdown(void)
else
{
FreeConsole();
return true;
color(Console::COLOR_LIGHTRED);
int ret = vfprintf( dfout_C, format, vl );
reset_color();
return ret;
}
int Console::print( const char* format, ... )
}
/// Print a formatted string, like printf, in red
int printerr(const char * format, ...)
{
va_list args;
va_start( args, format );
int ret = vfprintf( d->dfout_C, format, args );
int ret = vprinterr( format, args );
va_end( args );
return ret;
}
int Console::get_columns(void)
int get_columns(void)
{
CONSOLE_SCREEN_BUFFER_INFO inf = { 0 };
GetConsoleScreenBufferInfo(d->console_out, &inf);
GetConsoleScreenBufferInfo(console_out, &inf);
return (size_t)inf.dwSize.X;
}
int Console::get_rows(void)
int get_rows(void)
{
CONSOLE_SCREEN_BUFFER_INFO inf = { 0 };
GetConsoleScreenBufferInfo(d->console_out, &inf);
GetConsoleScreenBufferInfo(console_out, &inf);
return (size_t)inf.dwSize.Y;
}
void Console::clear()
void clear()
{
system("cls");
}
void Console::gotoxy(int x, int y)
void clearline()
{
CONSOLE_SCREEN_BUFFER_INFO inf = { 0 };
GetConsoleScreenBufferInfo(console_out, &inf);
// Blank to EOL
char* tmp = (char*)malloc(inf.dwSize.X);
memset(tmp, ' ', inf.dwSize.X);
output(tmp, inf.dwSize.X, 0, inf.dwCursorPosition.Y);
free(tmp);
COORD coord = {0, inf.dwCursorPosition.Y}; // Windows uses 0-based coordinates
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
void gotoxy(int x, int y)
{
COORD coord = {x-1, y-1}; // Windows uses 0-based coordinates
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
void Console::color(int index)
void color(int index)
{
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsole, index);
}
void Console::reset_color( void )
void reset_color( void )
{
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsole, d->default_attributes);
SetConsoleTextAttribute(hConsole, default_attributes);
}
void Console::cursor(bool enable)
void cursor(bool enable)
{
if(enable)
{
@ -250,33 +195,25 @@ void Console::cursor(bool enable)
}
}
void Console::msleep (unsigned int msec)
{
Sleep(msec);
}
int Console::enable_raw()
{
return 0;
}
void Console::disable_raw()
void output(const char* str, size_t len, int x, int y)
{
/* Nothing to do yet */
COORD pos = { (SHORT)x, (SHORT)y };
DWORD count = 0;
WriteConsoleOutputCharacterA(console_out, str, len, pos, &count);
}
void Console::prompt_refresh( const std::string& prompt, const std::string& buffer, size_t pos)
void prompt_refresh()
{
size_t cols = get_columns();
size_t plen = prompt.size();
const char * buf = buffer.c_str();
size_t len = buffer.size();
const char * buf = raw_buffer.c_str();
size_t len = raw_buffer.size();
while ((plen + pos) >= cols)
while ((plen + raw_cursor) >= cols)
{
buf++;
len--;
pos--;
raw_cursor--;
}
while (plen + len > cols)
{
@ -284,147 +221,327 @@ void Console::prompt_refresh( const std::string& prompt, const std::string& buff
}
CONSOLE_SCREEN_BUFFER_INFO inf = { 0 };
GetConsoleScreenBufferInfo(d->console_out, &inf);
d->output(prompt.c_str(), plen, 0, inf.dwCursorPosition.Y);
d->output(buf, len, plen, inf.dwCursorPosition.Y);
GetConsoleScreenBufferInfo(console_out, &inf);
output(prompt.c_str(), plen, 0, inf.dwCursorPosition.Y);
output(buf, len, plen, inf.dwCursorPosition.Y);
if (plen + len < (size_t)inf.dwSize.X)
{
/* Blank to EOL */
// Blank to EOL
char* tmp = (char*)malloc(inf.dwSize.X - (plen + len));
memset(tmp, ' ', inf.dwSize.X - (plen + len));
d->output(tmp, inf.dwSize.X - (plen + len), len + plen, inf.dwCursorPosition.Y);
output(tmp, inf.dwSize.X - (plen + len), len + plen, inf.dwCursorPosition.Y);
free(tmp);
}
inf.dwCursorPosition.X = (SHORT)(pos + plen);
SetConsoleCursorPosition(d->console_out, inf.dwCursorPosition);
inf.dwCursorPosition.X = (SHORT)(raw_cursor + plen);
SetConsoleCursorPosition(console_out, inf.dwCursorPosition);
}
int Console::prompt_loop(const std::string & prompt, std::string & buffer)
int prompt_loop()
{
buffer.clear(); // make sure the buffer is empty!
raw_buffer.clear(); // make sure the buffer is empty!
size_t plen = prompt.size();
size_t pos = 0;
raw_cursor = 0;
int history_index = 0;
/* The latest history entry is always our current buffer, that
* initially is just an empty string. */
// The latest history entry is always our current buffer, that
// initially is just an empty string.
const std::string empty;
history_add(empty);
CONSOLE_SCREEN_BUFFER_INFO inf = { 0 };
GetConsoleScreenBufferInfo(d->console_out, &inf);
GetConsoleScreenBufferInfo(console_out, &inf);
size_t cols = inf.dwSize.X;
d->output(prompt.c_str(), plen, 0, inf.dwCursorPosition.Y);
output(prompt.c_str(), plen, 0, inf.dwCursorPosition.Y);
inf.dwCursorPosition.X = (SHORT)plen;
SetConsoleCursorPosition(d->console_out, inf.dwCursorPosition);
SetConsoleCursorPosition(console_out, inf.dwCursorPosition);
while (1)
{
INPUT_RECORD rec;
DWORD count;
ReadConsoleInputA(d->console_in, &rec, 1, &count);
SDL_mutexV(wlock);
ReadConsoleInputA(console_in, &rec, 1, &count);
SDL_mutexP(wlock);
if (rec.EventType != KEY_EVENT || !rec.Event.KeyEvent.bKeyDown)
continue;
switch (rec.Event.KeyEvent.wVirtualKeyCode)
{
case VK_RETURN: // enter
d->history.pop_front();
return buffer.size();
history.pop_front();
return raw_buffer.size();
case VK_BACK: // backspace
if (pos > 0 && buffer.size() > 0)
if (raw_cursor > 0 && raw_buffer.size() > 0)
{
buffer.erase(pos-1,1);
pos--;
prompt_refresh(prompt,buffer,pos);
raw_buffer.erase(raw_cursor-1,1);
raw_cursor--;
prompt_refresh();
}
break;
case VK_LEFT: // left arrow
if (pos > 0)
if (raw_cursor > 0)
{
pos--;
prompt_refresh(prompt,buffer,pos);
raw_cursor--;
prompt_refresh();
}
break;
case VK_RIGHT: // right arrow
if (pos != buffer.size())
if (raw_cursor != raw_buffer.size())
{
pos++;
prompt_refresh(prompt,buffer,pos);
raw_cursor++;
prompt_refresh();
}
break;
case VK_UP:
case VK_DOWN:
/* up and down arrow: history */
if (d->history.size() > 1)
// up and down arrow: history
if (history.size() > 1)
{
/* Update the current history entry before to
* overwrite it with tne next one. */
d->history[history_index] = buffer;
/* Show the new entry */
// Update the current history entry before to
// overwrite it with tne next one.
history[history_index] = raw_buffer;
// Show the new entry
history_index += (rec.Event.KeyEvent.wVirtualKeyCode == VK_UP) ? 1 : -1;
if (history_index < 0)
{
history_index = 0;
break;
}
else if (history_index >= d->history.size())
else if (history_index >= history.size())
{
history_index = d->history.size()-1;
history_index = history.size()-1;
break;
}
buffer = d->history[history_index];
pos = buffer.size();
prompt_refresh(prompt,buffer,pos);
raw_buffer = history[history_index];
raw_cursor = raw_buffer.size();
prompt_refresh();
}
break;
case VK_DELETE:
// delete
if (buffer.size() > 0 && pos < buffer.size())
if (raw_buffer.size() > 0 && raw_cursor < raw_buffer.size())
{
buffer.erase(pos,1);
prompt_refresh(prompt,buffer,pos);
raw_buffer.erase(raw_cursor,1);
prompt_refresh();
}
break;
case VK_HOME:
pos = 0;
prompt_refresh(prompt,buffer,pos);
raw_cursor = 0;
prompt_refresh();
break;
case VK_END:
pos = buffer.size();
prompt_refresh(prompt,buffer,pos);
raw_cursor = raw_buffer.size();
prompt_refresh();
break;
default:
if (rec.Event.KeyEvent.uChar.AsciiChar < ' ' ||
rec.Event.KeyEvent.uChar.AsciiChar > '~')
continue;
if (buffer.size() == pos)
buffer.append(1,rec.Event.KeyEvent.uChar.AsciiChar);
if (raw_buffer.size() == raw_cursor)
raw_buffer.append(1,rec.Event.KeyEvent.uChar.AsciiChar);
else
buffer.insert(pos,1,rec.Event.KeyEvent.uChar.AsciiChar);
pos++;
prompt_refresh(prompt,buffer,pos);
raw_buffer.insert(raw_cursor,1,rec.Event.KeyEvent.uChar.AsciiChar);
raw_cursor++;
prompt_refresh();
break;
}
}
}
int lineedit(const std::string & prompt, std::string & output)
{
output.clear();
int count;
state = con_lineedit;
this->prompt = prompt;
count = prompt_loop();
if(count != -1)
output = raw_buffer;
state = con_unclaimed;
print("\n");
return count;
}
// push to front, remove from back if we are above maximum. ignore immediate duplicates
void Console::history_add(const std::string & command)
void history_add(const std::string & command)
{
if(d->history.front() == command)
// if current command = last in history -> do not add. Always add if history is empty.
if(!history.empty() && history.front() == command)
return;
d->history.push_front(command);
if(d->history.size() > 100)
d->history.pop_back();
history.push_front(command);
if(history.size() > 100)
history.pop_back();
}
FILE * dfout_C;
duthomhas::stdiobuf * stream_o;
int rawmode; /* for atexit() function to check if restore is needed*/
std::deque <std::string> history;
HANDLE console_in;
HANDLE console_out;
HWND ConsoleWindow;
WORD default_attributes;
// current state
enum console_state
{
con_unclaimed,
con_lineedit
} state;
std::string prompt; // current prompt string
std::string raw_buffer; // current raw mode buffer
int raw_cursor; // cursor position in the buffer
// locks
SDL::Mutex *wlock;
};
}
Console::Console():std::ostream(0), std::ios(0)
{
d = 0;
}
Console::~Console()
{
}
bool Console::init(void)
{
d = new Private();
int hConHandle;
long lStdHandle;
CONSOLE_SCREEN_BUFFER_INFO coninfo;
FILE *fp;
DWORD oldMode, newMode;
// Allocate a console!
AllocConsole();
d->ConsoleWindow = GetConsoleWindow();
HMENU hm = GetSystemMenu(d->ConsoleWindow,false);
DeleteMenu(hm, SC_CLOSE, MF_BYCOMMAND);
// set the screen buffer to be big enough to let us scroll text
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
d->default_attributes = coninfo.wAttributes;
coninfo.dwSize.Y = MAX_CONSOLE_LINES; // How many lines do you want to have in the console buffer
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
// redirect unbuffered STDOUT to the console
d->console_out = GetStdHandle(STD_OUTPUT_HANDLE);
lStdHandle = (long)d->console_out;
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
d->dfout_C = _fdopen( hConHandle, "w" );
setvbuf( d->dfout_C, NULL, _IONBF, 0 );
// redirect unbuffered STDIN to the console
d->console_in = GetStdHandle(STD_INPUT_HANDLE);
lStdHandle = (long)d->console_in;
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
fp = _fdopen( hConHandle, "r" );
*stdin = *fp;
setvbuf( stdin, NULL, _IONBF, 0 );
GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),&oldMode);
newMode = oldMode | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),newMode);
std::ios::sync_with_stdio();
// make our own weird streams so our IO isn't redirected
d->stream_o = new duthomhas::stdiobuf(d->dfout_C);
rdbuf(d->stream_o);
std::cin.tie(this);
d->wlock = SDL_CreateMutex();
clear();
return true;
}
// FIXME: looks awfully empty, doesn't it?
bool Console::shutdown(void)
{
FreeConsole();
return true;
}
int Console::print( const char* format, ... )
{
va_list args;
SDL_mutexP(d->wlock);
va_start( args, format );
int ret = d->vprint(format, args);
va_end(args);
SDL_mutexV(d->wlock);
return ret;
}
int Console::printerr( const char* format, ... )
{
va_list args;
SDL_mutexP(d->wlock);
va_start( args, format );
int ret = d->vprinterr(format, args);
va_end(args);
SDL_mutexV(d->wlock);
return ret;
}
int Console::get_columns(void)
{
return d->get_columns();
}
int Console::get_rows(void)
{
return d->get_rows();
}
void Console::clear()
{
SDL_mutexP(d->wlock);
d->clear();
SDL_mutexV(d->wlock);
}
void Console::gotoxy(int x, int y)
{
SDL_mutexP(d->wlock);
d->gotoxy(x,y);
SDL_mutexV(d->wlock);
}
void Console::color(color_value index)
{
SDL_mutexP(d->wlock);
d->color(index);
SDL_mutexV(d->wlock);
}
void Console::reset_color( void )
{
SDL_mutexP(d->wlock);
d->reset_color();
SDL_mutexV(d->wlock);
}
void Console::cursor(bool enable)
{
SDL_mutexP(d->wlock);
d->cursor(enable);
SDL_mutexV(d->wlock);
}
// push to front, remove from back if we are above maximum. ignore immediate duplicates
void Console::history_add(const std::string & command)
{
SDL_mutexP(d->wlock);
d->history_add(command);
SDL_mutexV(d->wlock);
}
int Console::lineedit(const std::string & prompt, std::string & output)
{
output.clear();
int count;
if (enable_raw() == -1)
return -1;
count = prompt_loop(prompt,output);
disable_raw();
*this << std::endl;
return count;
SDL_mutexP(d->wlock);
int ret = d->lineedit(prompt,output);
SDL_mutexV(d->wlock);
return ret;
}
void Console::msleep (unsigned int msec)
{
Sleep(msec);
}