Integrate linenoise into Console - Linux part

develop
Petr Mrázek 2011-07-13 11:45:30 +02:00
parent 1b011cdf6c
commit 630b746cfe
13 changed files with 684 additions and 121 deletions

@ -140,3 +140,45 @@ DAMAGE.
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------
Parts of dfhack are based on linenoise:
linenoise.c -- 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:
http://github.com/antirez/linenoise
Does a number of crazy assumptions that happen to be true in 99.9999% of
the 2010 UNIX computers around.
------------------------------------------------------------------------
Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -151,6 +151,9 @@ IF(UNIX)
COMMAND ${CMAKE_COMMAND} -E make_directory ${DFHACK_OUTPUT_DIR}
COMMAND ${CMAKE_COMMAND} -E copy ${dfhack_SOURCE_DIR}/package/linux/dfhack ${DFHACK_OUTPUT_DIR})
ADD_DEPENDENCIES(dfhack prepare_UNIX)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack
DESTINATION ${DFHACK_LIBRARY_DESTINATION}) #linux: share/dfhack
ENDIF()
install(TARGETS dfhack

@ -22,42 +22,178 @@ must not be misrepresented as being the original software.
distribution.
*/
/*
Parts of this code are based on linenoise:
linenoise.c -- 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:
http://github.com/antirez/linenoise
Does a number of crazy assumptions that happen to be true in 99.9999% of
the 2010 UNIX computers around.
------------------------------------------------------------------------
Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "dfhack/Console.h"
#include <cstdio>
#include <cstdlib>
#include <sstream>
#include <stdio.h>
#include <string.h>
#include <string>
#include <stdarg.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <errno.h>
using namespace DFHack;
duthomhas::stdiostream dfout;
FILE * dfout_C = 0;
duthomhas::stdiobuf * stream_o = 0;
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE 4096
static const char *unsupported_term[] = {"dumb","cons25",NULL};
namespace DFHack
{
class Private
{
public:
Private()
{
dfout_C = 0;
stream_o = 0;
rawmode = 0;
history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
history_len = 0;
history = NULL;
};
int get_columns(void)
{
winsize ws;
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 80;
return ws.ws_col;
}
int get_rows(void)
{
winsize ws;
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 25;
return ws.ws_row;
}
void clear()
{
const char * clr = "\033c\033[3J\033[H";
write(STDIN_FILENO,clr,strlen(clr));
}
int isUnsupportedTerm(void)
{
char *term = getenv("TERM");
int j;
if (term == NULL) return 0;
for (j = 0; unsupported_term[j]; j++)
if (!strcasecmp(term,unsupported_term[j])) return 1;
return 0;
}
FILE * dfout_C;
duthomhas::stdiobuf * stream_o;
termios orig_termios; /* in order to restore at exit */
int rawmode; /* for atexit() function to check if restore is needed*/
int history_max_len;
int history_len;
char **history;
};
}
// FIXME: prime candidate for being a singleton...
Console::Console()
{
d = new Private();
}
Console::~Console()
{
delete d;
}
bool Console::init(void)
{
// make our own weird streams so our IO isn't redirected
dfout_C = fopen("/dev/tty", "w");
stream_o = new duthomhas::stdiobuf(dfout_C);
dfout.rdbuf(stream_o);
std::cin.tie(&dfout);
d->dfout_C = fopen("/dev/tty", "w");
d->stream_o = new duthomhas::stdiobuf(d->dfout_C);
rdbuf(d->stream_o);
std::cin.tie(this);
clear();
// result is a terminal controlled by the parasitic code!
}
Console::~Console()
bool Console::shutdown(void)
{
if(d->rawmode)
disable_raw();
*this << std::endl;
}
int Console::print( const char* format, ... )
{
va_list args;
va_start( args, format );
int ret = vfprintf( d->dfout_C, format, args );
va_end( args );
return ret;
}
int Console::get_columns(void)
{
return d->get_columns();
}
int Console::get_rows(void)
{
return d->get_rows();
}
void Console::clear()
{
dfout << "\033c";
dfout << "\033[3J\033[H";
*this << "\033c";
*this << "\033[3J\033[H";
}
void Console::gotoxy(int x, int y)
{
std::ostringstream oss;
oss << "\033[" << y << ";" << x << "H";
dfout << oss.str();
*this << oss.str();
}
const char * ANSI_CLS = "\033[2J";
@ -105,12 +241,12 @@ const char * getANSIColor(const int c)
void Console::color(int index)
{
dfout << getANSIColor(index);
*this << getANSIColor(index);
}
void Console::reset_color( void )
{
dfout << RESETCOLOR;
*this << RESETCOLOR;
}
@ -118,11 +254,11 @@ void Console::cursor(bool enable)
{
if(enable)
{
dfout <<"\033[?25h";
*this <<"\033[?25h";
}
else
{
dfout <<"\033[?25l";
*this <<"\033[?25l";
}
}
@ -131,3 +267,326 @@ void Console::msleep (unsigned int msec)
if (msec > 1000) sleep(msec/1000000);
usleep((msec % 1000000) * 1000);
}
int Console::enable_raw()
{
struct termios raw;
if (!isatty(STDIN_FILENO)) goto fatal;
if (tcgetattr(STDIN_FILENO,&d->orig_termios) == -1) goto fatal;
raw = d->orig_termios; /* modify the original mode */
/* input modes: no break, no CR to NL, no parity check, no strip char,
* no start/stop output control. */
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
/* output modes - disable post processing */
raw.c_oflag &= ~(OPOST);
/* control modes - set 8 bit chars */
raw.c_cflag |= (CS8);
/* local modes - choing off, canonical off, no extended functions,
* no signal chars (^Z,^C) */
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
/* control chars - set return condition: min number of bytes and timer.
* We want read to return every single byte, without timeout. */
raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
/* put terminal in raw mode after flushing */
if (tcsetattr(STDIN_FILENO,TCSAFLUSH,&raw) < 0) goto fatal;
d->rawmode = 1;
return 0;
fatal:
errno = ENOTTY;
return -1;
}
void Console::disable_raw()
{
/* Don't even check the return value as it's too late. */
if (d->rawmode && tcsetattr(STDIN_FILENO,TCSAFLUSH,&d->orig_termios) != -1)
d->rawmode = 0;
}
void Console::prompt_refresh( const std::string& prompt, const std::string& buffer, size_t pos)
{
char seq[64];
int cols = get_columns();
int plen = prompt.size();
const char * buf = buffer.c_str();
int len = buffer.size();
// Use math! This is silly.
while((plen+pos) >= cols)
{
buf++;
len--;
pos--;
}
while (plen+len > cols)
{
len--;
}
/* Cursor to left edge */
snprintf(seq,64,"\x1b[1G");
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
/* Write the prompt and the current buffer content */
if (::write(STDIN_FILENO,prompt.c_str(),plen) == -1) return;
if (::write(STDIN_FILENO,buf,len) == -1) return;
/* Erase to right */
snprintf(seq,64,"\x1b[0K");
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
/* Move cursor to original position. */
snprintf(seq,64,"\x1b[1G\x1b[%dC", (int)(pos+plen));
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
}
int Console::prompt_loop(const std::string & prompt, std::string & buffer)
{
int fd = STDIN_FILENO;
size_t plen = prompt.size();
size_t pos = 0;
size_t cols = d->get_columns();
int history_index = 0;
/* The latest history entry is always our current buffer, that
* initially is just an empty string. */
history_add("");
if (::write(fd,prompt.c_str(),plen) == -1) return -1;
while(1)
{
char c;
int nread;
char seq[2], seq2[2];
nread = ::read(fd,&c,1);
if (nread <= 0) return buffer.size();
/* Only autocomplete when the callback is set. It returns < 0 when
* there was an error reading from fd. Otherwise it will return the
* character that should be handled next. */
if (c == 9)
{
/*
if( completionCallback != NULL) {
c = completeLine(fd,prompt,buf,buflen,&len,&pos,cols);
// Return on errors
if (c < 0) return len;
// Read next character when 0
if (c == 0) continue;
}
else
{
// ignore tab
continue;
}
*/
// just ignore tabs
continue;
}
switch(c) {
case 13: /* enter */
history.pop_front();
return buffer.size();
case 3: /* ctrl-c */
errno = EAGAIN;
return -1;
case 127: /* backspace */
case 8: /* ctrl-h */
if (pos > 0 && buffer.size() > 0)
{
buffer.erase(pos-1,1);
pos--;
prompt_refresh(prompt,buffer,pos);
}
break;
// I fail to see how is this useful to anyone but hardcore emacs users
/*
case 4: // ctrl-d, remove char at right of cursor
if (len > 1 && pos < (len-1)) {
memmove(buf+pos,buf+pos+1,len-pos);
len--;
buf[len] = '\0';
prompt_refresh(prompt,buffer,pos);
} else if (len == 0) {
history_len--;
free(history[history_len]);
return -1;
}
break;
case 20: // ctrl-t
if (pos > 0 && pos < len) {
int aux = buf[pos-1];
buf[pos-1] = buf[pos];
buf[pos] = aux;
if (pos != len-1) pos++;
prompt_refresh(prompt,buffer,pos);
}
break;
case 2: // ctrl-b
goto left_arrow;
case 6: // ctrl-f
goto right_arrow;
case 16: // ctrl-p
seq[1] = 65;
goto up_down_arrow;
case 14: // ctrl-n
seq[1] = 66;
goto up_down_arrow;
break;
*/
case 27: /* escape sequence */
if (::read(fd,seq,2) == -1) break;
if(seq[0] == '[')
{
if (seq[1] == 'D')
{
left_arrow:
if (pos > 0)
{
pos--;
prompt_refresh(prompt,buffer,pos);
}
}
else if ( seq[1] == 'C')
{
right_arrow:
/* right arrow */
if (pos != buffer.size())
{
pos++;
prompt_refresh(prompt,buffer,pos);
}
}
else if (seq[1] == 'A' || seq[1] == 'B')
{
up_down_arrow:
/* up and down arrow: history */
if (history.size() > 1)
{
/* Update the current history entry before to
* overwrite it with tne next one. */
history[history_index] = buffer;
/* Show the new entry */
history_index += (seq[1] == 'A') ? 1 : -1;
if (history_index < 0)
{
history_index = 0;
break;
}
else if (history_index >= history.size())
{
history_index = history.size()-1;
break;
}
buffer = history[history_index];
pos = buffer.size();
prompt_refresh(prompt,buffer,pos);
}
}
else if(seq[1] == 'H')
{
// home
pos = 0;
prompt_refresh(prompt,buffer,pos);
}
else if(seq[1] == 'F')
{
// end
pos = buffer.size();
prompt_refresh(prompt,buffer,pos);
}
else if (seq[1] > '0' && seq[1] < '7')
{
// extended escape
if (::read(fd,seq2,2) == -1) break;
if (seq2[0] == '~' && seq[1] == '3')
{
// delete
if (buffer.size() > 0 && pos < buffer.size())
{
buffer.erase(pos,1);
prompt_refresh(prompt,buffer,pos);
}
}
}
}
break;
default:
if (buffer.size() == pos)
{
buffer.append(1,c);
pos++;
if (plen+buffer.size() < cols)
{
/* Avoid a full update of the line in the
* trivial case. */
if (::write(fd,&c,1) == -1) return -1;
}
else
{
prompt_refresh(prompt,buffer,pos);
}
}
else
{
buffer.insert(pos,1,c);
pos++;
prompt_refresh(prompt,buffer,pos);
}
break;
case 21: // Ctrl+u, delete the whole line.
buffer.clear();
pos = 0;
prompt_refresh(prompt,buffer,pos);
break;
case 11: // Ctrl+k, delete from current to end of line.
buffer.erase(pos);
prompt_refresh(prompt,buffer,pos);
break;
case 1: // Ctrl+a, go to the start of the line
pos = 0;
prompt_refresh(prompt,buffer,pos);
break;
case 5: // ctrl+e, go to the end of the line
pos = buffer.size();
prompt_refresh(prompt,buffer,pos);
break;
case 12: // ctrl+l, clear screen
clear();
prompt_refresh(prompt,buffer,pos);
}
}
return buffer.size();
}
// push to front, remove from back if we are above maximum. ignore immediate duplicates
void Console::history_add(const std::string & command)
{
if(history.front() == command)
return;
history.push_front(command);
if(history.size() > 100)
history.pop_back();
}
int Console::lineedit(const std::string & prompt, std::string & output)
{
output.clear();
int count;
if (d->isUnsupportedTerm() || !isatty(STDIN_FILENO))
{
*this << prompt;
flush();
std::getline(std::cin, output);
return output.size();
}
else
{
if (enable_raw() == -1) return 0;
count = prompt_loop(prompt, output);
disable_raw();
*this << std::endl;
return output.size();
}
}

@ -125,46 +125,44 @@ int fIOthread(void * iodata)
PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr;
if(plug_mgr == 0 || core == 0)
{
dfout << "Something horrible happened to the plugin manager in Core's constructor..." << std::endl;
core->con << "Something horrible happened to the plugin manager in Core's constructor..." << std::endl;
return 0;
}
fprintf(dfout_C,"DFHack is ready. Have a nice day! Type in '?' or 'help' for help.\n");
core->con.print("DFHack is ready. Have a nice day! Type in '?' or 'help' for help.\n");
//dfterm << << endl;
int clueless_counter = 0;
while (true)
{
string command = "";
core->con.lineedit("[DFHack]# ",command);
core->con.history_add(command);
//core->con <<"[DFHack]# ";
//char * line = linenoise("[DFHack]# ", core->con.dfout_C);
// dfout <<"[DFHack]# ";
char * line = linenoise("[DFHack]# ", dfout_C);
// dfout <<"[DFHack]# ";
/*
if (line)
{
command=line;
linenoiseHistoryAdd(line);
free(line);
}
}*/
//getline(cin, command);
if (cin.eof())
{
command = "q";
dfout << std::endl; // No newline from the user here!
}
if(command=="help" || command == "?")
{
dfout << "Available commands" << endl;
dfout << "------------------" << endl;
core->con << "Available commands" << endl;
core->con << "------------------" << endl;
for(int i = 0; i < plug_mgr->size();i++)
{
const Plugin * plug = (plug_mgr->operator[](i));
if(!plug->size())
continue;
dfout << "Plugin " << plug->getName() << " :" << std::endl;
core->con << "Plugin " << plug->getName() << " :" << std::endl;
for (int j = 0; j < plug->size();j++)
{
const PluginCommand & pcmd = (plug->operator[](j));
dfout << setw(12) << pcmd.name << "| " << pcmd.description << endl;
core->con << setw(12) << pcmd.name << "| " << pcmd.description << endl;
}
dfout << endl;
core->con << endl;
}
}
else if( command == "" )
@ -186,18 +184,18 @@ int fIOthread(void * iodata)
command_result res = plug_mgr->InvokeCommand(first, parts);
if(res == CR_NOT_IMPLEMENTED)
{
dfout << "Invalid command." << endl;
core->con << "Invalid command." << endl;
clueless_counter ++;
}
else if(res == CR_FAILURE)
{
dfout << "ERROR!" << endl;
core->con << "ERROR!" << endl;
}
}
}
if(clueless_counter == 3)
{
dfout << "Do 'help' or '?' for the list of available commands." << endl;
core->con << "Do 'help' or '?' for the list of available commands." << endl;
clueless_counter = 0;
}
}
@ -206,7 +204,6 @@ int fIOthread(void * iodata)
Core::Core()
{
// init the console. This must be always the first step!
con = 0;
plug_mgr = 0;
vif = 0;
p = 0;
@ -228,13 +225,13 @@ Core::Core()
bool Core::Init()
{
// init the console. This must be always the first step!
con = new Console();
con.init();
// find out what we are...
vif = new DFHack::VersionInfoFactory("Memory.xml");
p = new DFHack::Process(vif);
if (!p->isIdentified())
{
dfout << "Couldn't identify this version of DF." << std::endl;
con << "Couldn't identify this version of DF." << std::endl;
errorstate = true;
delete p;
p = NULL;
@ -246,7 +243,7 @@ bool Core::Init()
AccessMutex = SDL_CreateMutex();
if(!AccessMutex)
{
dfout << "Mutex creation failed." << std::endl;
con << "Mutex creation failed." << std::endl;
errorstate = true;
return false;
}
@ -255,7 +252,7 @@ bool Core::Init()
plug_mgr = new PluginManager(this);
if(!plug_mgr)
{
dfout << "Failed to create the Plugin Manager." << std::endl;
con << "Failed to create the Plugin Manager." << std::endl;
errorstate = true;
return false;
}
@ -367,11 +364,7 @@ int Core::Shutdown ( void )
}
allModules.clear();
memset(&(s_mods), 0, sizeof(s_mods));
dfout << std::endl;
// kill the console object
if(con)
delete con;
con = 0;
con.shutdown();
return -1;
}

@ -47,7 +47,6 @@ static int getdir (string dir, vector<string> &files)
struct dirent *dirp;
if((dp = opendir(dir.c_str())) == NULL)
{
dfout << "Error(" << errno << ") opening " << dir << endl;
return errno;
}
while ((dirp = readdir(dp)) != NULL) {
@ -81,20 +80,20 @@ Plugin::Plugin(Core * core, const std::string & file)
DFLibrary * plug = OpenPlugin(file.c_str());
if(!plug)
{
dfout << "Can't load plugin " << filename << endl;
core->con << "Can't load plugin " << filename << endl;
return;
}
const char * (*_PlugName)() =(const char * (*)()) LookupPlugin(plug, "plugin_name");
if(!_PlugName)
{
dfout << "Plugin " << filename << " has no name." << endl;
core->con << "Plugin " << filename << " has no name." << endl;
ClosePlugin(plug);
return;
}
plugin_init = (command_result (*)(Core *, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init");
if(!plugin_init)
{
dfout << "Plugin " << filename << " has no init function." << endl;
core->con << "Plugin " << filename << " has no init function." << endl;
ClosePlugin(plug);
return;
}

@ -112,7 +112,8 @@ char **history = NULL;
static void linenoiseAtExit(void);
int linenoiseHistoryAdd(const char *line);
static int isUnsupportedTerm(void) {
static int isUnsupportedTerm(void)
{
char *term = getenv("TERM");
int j;
@ -286,7 +287,47 @@ void linenoiseClearScreen(void) {
/* nothing to do, just to avoid warning. */
}
}
/*
static int selread(int fd, char *buf, size_t buflen)
{
int n;
int start = 0;
fd_set input;
struct timeval timeout;
// Initialize the input set
FD_ZERO(&input);
FD_SET(fd, &input);
repeat_select:
timeout.tv_sec = 1;
timeout.tv_usec = 0;
n = select(fd+1, &input, NULL, NULL, &timeout);
if(n == 0 || n == -1 && errno == EINTR)
{
// lock mutex
// copy end variable
// unlock mutex
// if end, return -2
// else
goto repeat_select;
}
else if (n > 0)
{
int nread = read(fd,buf + start,buflen);
if (nread == -1)
return -1; // it's an ugly error
else if(nread < buflen)
{
start += nread;
buflen -= nread;
goto repeat_select;
}
else return nread + start;
}
else
return -1;
}
*/
static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) {
size_t plen = strlen(prompt);
size_t pos = 0;
@ -538,7 +579,8 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt, FILE * out
return count;
}
char *linenoise(const char *prompt, FILE * out) {
char *linenoise(const char *prompt, FILE * out)
{
char buf[LINENOISE_MAX_LINE];
int count;
@ -575,11 +617,13 @@ void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
}
/* Using a circular buffer is smarter, but a bit more complex to handle. */
int linenoiseHistoryAdd(const char *line) {
int linenoiseHistoryAdd(const char *line)
{
char *linecopy;
if (history_max_len == 0) return 0;
if (history == NULL) {
if (history == NULL)
{
history = (char**)malloc(sizeof(char*)*history_max_len);
if (history == NULL) return 0;
memset(history,0,(sizeof(char*)*history_max_len));
@ -596,7 +640,8 @@ int linenoiseHistoryAdd(const char *line) {
return 1;
}
int linenoiseHistorySetMaxLen(int len) {
int linenoiseHistorySetMaxLen(int len)
{
char **newHistory;
if (len < 1) return 0;

@ -25,19 +25,23 @@ distribution.
#pragma once
#include "dfhack/Pragma.h"
#include "dfhack/Export.h"
#include "dfhack/Core.h"
#include "dfhack/extra/stdiostream.h"
extern DFHACK_EXPORT duthomhas::stdiostream dfout;
extern DFHACK_EXPORT FILE * dfout_C;
#include <deque>
namespace DFHack
{
class DFHACK_EXPORT Console
class Private;
class DFHACK_EXPORT Console : public duthomhas::stdiostream
{
public:
Console();
~Console();
/// initialize the console
bool init( void );
/// shutdown the console
bool shutdown( void );
/// Print a formatted string, like printf
int print(const char * format, ...);
/// Clear the console, along with its scrollback
void clear();
/// Position cursor at x,y. 1,1 = top left corner
@ -50,5 +54,23 @@ namespace DFHack
void cursor(bool enable = true);
/// Waits given number of milliseconds before continuing.
void msleep(unsigned int msec);
/// get the current number of columns
int get_columns(void);
/// get the current number of rows
int get_rows(void);
/// beep. maybe?
//void beep (void);
/// A simple line edit (raw mode)
int lineedit(const std::string& prompt, std::string& output);
/// add a command to the history
void history_add(const std::string& command);
private:
int prompt_loop(const std::string & prompt, std::string & buffer);
void prompt_refresh( const std::string & prompt, const std::string & buffer, size_t pos);
int enable_raw();
void disable_raw();
std::deque <std::string> history;
void history_clear();
Private * d;
};
}

@ -31,7 +31,7 @@ distribution.
#include <stack>
#include <map>
#include <stdint.h>
#include "dfhack/Console.h"
namespace DFHack
{
@ -112,7 +112,7 @@ namespace DFHack
DFHack::Process * p;
DFHack::VersionInfo * vinfo;
DFHack::Console * con;
DFHack::Console con;
private:
Core();
bool Init();

@ -51,7 +51,7 @@ DFhackCExport command_result cleanmap (Core * c, vector <string> & parameters)
// init the map
if(!Mapz->Start())
{
dfout << "Can't init map." << std::endl;
c->con << "Can't init map." << std::endl;
c->Resume();
return CR_FAILURE;
}

@ -41,7 +41,7 @@ struct compare_pair_second
};
void printMats(MatMap &mat, std::vector<DFHack::t_matgloss> &materials)
void printMats(DFHack::Console & con, MatMap &mat, std::vector<DFHack::t_matgloss> &materials)
{
unsigned int total = 0;
MatSorter sorting_vector;
@ -54,15 +54,15 @@ void printMats(MatMap &mat, std::vector<DFHack::t_matgloss> &materials)
{
if(it->first >= materials.size())
{
dfout << "Bad index: " << it->first << " out of " << materials.size() << endl;
con << "Bad index: " << it->first << " out of " << materials.size() << endl;
continue;
}
DFHack::t_matgloss mat = materials[it->first];
dfout << std::setw(25) << mat.id << " : " << it->second << std::endl;
con << std::setw(25) << mat.id << " : " << it->second << std::endl;
total += it->second;
}
dfout << ">>> TOTAL = " << total << std::endl << std::endl;
con << ">>> TOTAL = " << total << std::endl << std::endl;
}
DFhackCExport command_result prospector (Core * c, vector <string> & parameters);
@ -98,7 +98,7 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector <string> & par
DFHack::Maps *maps = c->getMaps();
if (!maps->Start())
{
dfout << "Cannot get map info!" << std::endl;
c->con << "Cannot get map info!" << std::endl;
c->Resume();
return CR_FAILURE;
}
@ -108,13 +108,13 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector <string> & par
DFHack::Materials *mats = c->getMaterials();
if (!mats->ReadInorganicMaterials())
{
dfout << "Unable to read inorganic material definitons!" << std::endl;
c->con << "Unable to read inorganic material definitons!" << std::endl;
c->Resume();
return CR_FAILURE;
}
if (showPlants && !mats->ReadOrganicMaterials())
{
dfout << "Unable to read organic material definitons; plants won't be listed!" << std::endl;
c->con << "Unable to read organic material definitons; plants won't be listed!" << std::endl;
showPlants = false;
}
@ -134,12 +134,12 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector <string> & par
if (!(showSlade && maps->ReadGlobalFeatures(globalFeatures)))
{
dfout << "Unable to read global features; slade won't be listed!" << std::endl;
c->con << "Unable to read global features; slade won't be listed!" << std::endl;
}
if (!maps->ReadLocalFeatures(localFeatures))
{
dfout << "Unable to read local features; adamantine "
c->con << "Unable to read local features; adamantine "
<< (showTemple ? "and demon temples " : "")
<< "won't be listed!" << std::endl;
}
@ -148,7 +148,7 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector <string> & par
DFHack::Vegetation *veg = c->getVegetation();
if (showPlants && !veg->Start())
{
dfout << "Unable to read vegetation; plants won't be listed!" << std::endl;
c->con << "Unable to read vegetation; plants won't be listed!" << std::endl;
}
for(uint32_t z = 0; z < z_max; z++)
@ -217,7 +217,7 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector <string> & par
if (!info)
{
dfout << "Bad type: " << type << std::endl;
c->con << "Bad type: " << type << std::endl;
continue;
}
@ -306,39 +306,39 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector <string> & par
MatMap::const_iterator it;
dfout << "Base materials:" << std::endl;
c->con << "Base materials:" << std::endl;
for (it = baseMats.begin(); it != baseMats.end(); ++it)
{
dfout << std::setw(25) << DFHack::TileMaterialString[it->first] << " : " << it->second << std::endl;
c->con << std::setw(25) << DFHack::TileMaterialString[it->first] << " : " << it->second << std::endl;
}
dfout << std::endl << "Layer materials:" << std::endl;
printMats(layerMats, mats->inorganic);
c->con << std::endl << "Layer materials:" << std::endl;
printMats(c->con, layerMats, mats->inorganic);
dfout << "Vein materials:" << std::endl;
printMats(veinMats, mats->inorganic);
c->con << "Vein materials:" << std::endl;
printMats(c->con, veinMats, mats->inorganic);
if (showPlants)
{
dfout << "Shrubs:" << std::endl;
printMats(plantMats, mats->organic);
dfout << "Wood in trees:" << std::endl;
printMats(treeMats, mats->organic);
c->con << "Shrubs:" << std::endl;
printMats(c->con, plantMats, mats->organic);
c->con << "Wood in trees:" << std::endl;
printMats(c->con, treeMats, mats->organic);
}
if (hasAquifer)
{
dfout << "Has aquifer" << std::endl;
c->con << "Has aquifer" << std::endl;
}
if (hasDemonTemple)
{
dfout << "Has demon temple" << std::endl;
c->con << "Has demon temple" << std::endl;
}
if (hasLair)
{
dfout << "Has lair" << std::endl;
c->con << "Has lair" << std::endl;
}
// Cleanup
@ -349,6 +349,6 @@ DFhackCExport command_result prospector (DFHack::Core * c, vector <string> & par
mats->Finish();
maps->Finish();
c->Resume();
dfout << std::endl;
c->con << std::endl;
return CR_OK;
}

@ -104,7 +104,7 @@ DFhackCExport command_result reveal(DFHack::Core * c, std::vector<std::string> &
if(revealed != NOT_REVEALED)
{
dfout << "Map is already revealed or this is a different map." << std::endl;
c->con << "Map is already revealed or this is a different map." << std::endl;
return CR_FAILURE;
}
@ -115,20 +115,20 @@ DFhackCExport command_result reveal(DFHack::Core * c, std::vector<std::string> &
World->ReadGameMode(gm);
if(gm.g_mode != GAMEMODE_DWARF)
{
dfout << "Only in fortress mode." << std::endl;
c->con << "Only in fortress mode." << std::endl;
c->Resume();
return CR_FAILURE;
}
if(!Maps->Start())
{
dfout << "Can't init map." << std::endl;
c->con << "Can't init map." << std::endl;
c->Resume();
return CR_FAILURE;
}
if(no_hell && !Maps->StartFeatures())
{
dfout << "Unable to read local features; can't reveal map safely" << std::endl;
c->con << "Unable to read local features; can't reveal map safely" << std::endl;
c->Resume();
return CR_FAILURE;
}
@ -175,10 +175,10 @@ DFhackCExport command_result reveal(DFHack::Core * c, std::vector<std::string> &
World->SetPauseState(true);
}
c->Resume();
dfout << "Map revealed." << std::endl;
c->con << "Map revealed." << std::endl;
if(!no_hell)
dfout << "Unpausing can unleash the forces of hell, so it has been temporarily disabled." << std::endl;
dfout << "Run 'unreveal' to revert to previous state." << std::endl;
c->con << "Unpausing can unleash the forces of hell, so it has been temporarily disabled." << std::endl;
c->con << "Run 'unreveal' to revert to previous state." << std::endl;
return CR_OK;
}
@ -187,7 +187,7 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector<std::string>
DFHack::designations40d designations;
if(!revealed)
{
dfout << "There's nothing to revert!" << std::endl;
c->con << "There's nothing to revert!" << std::endl;
return CR_FAILURE;
}
c->Suspend();
@ -197,14 +197,14 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector<std::string>
World->ReadGameMode(gm);
if(gm.g_mode != GAMEMODE_DWARF)
{
dfout << "Only in fortress mode." << std::endl;
c->con << "Only in fortress mode." << std::endl;
c->Resume();
return CR_FAILURE;
}
Maps = c->getMaps();
if(!Maps->Start())
{
dfout << "Can't init map." << std::endl;
c->con << "Can't init map." << std::endl;
c->Resume();
return CR_FAILURE;
}
@ -214,7 +214,7 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector<std::string>
Maps->getSize(x_max_b,y_max_b,z_max_b);
if(x_max != x_max_b || y_max != y_max_b || z_max != z_max_b)
{
dfout << "The map is not of the same size..." << std::endl;
c->con << "The map is not of the same size..." << std::endl;
c->Resume();
return CR_FAILURE;
}
@ -233,7 +233,7 @@ DFhackCExport command_result unreveal(DFHack::Core * c, std::vector<std::string>
// give back memory.
hidesaved.clear();
revealed = NOT_REVEALED;
dfout << "Map hidden!" << std::endl;
c->con << "Map hidden!" << std::endl;
c->Resume();
return CR_OK;
}

@ -49,7 +49,7 @@ DFhackCExport command_result vdig (Core * c, vector <string> & parameters)
// init the map
if(!Maps->Start())
{
dfout << "Can't init map. Make sure you have a map loaded in DF." << std::endl;
c->con << "Can't init map. Make sure you have a map loaded in DF.\n";
c->Resume();
return CR_FAILURE;
}
@ -61,14 +61,14 @@ DFhackCExport command_result vdig (Core * c, vector <string> & parameters)
Gui->getCursorCoords(cx,cy,cz);
while(cx == -30000)
{
dfout << "Cursor is not active. Point the cursor at a vein." << std::endl;
c->con << "Cursor is not active. Point the cursor at a vein.\n";
c->Resume();
return CR_FAILURE;
}
DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz);
if(xy.x == 0 || xy.x == tx_max - 1 || xy.y == 0 || xy.y == ty_max - 1)
{
dfout << "I won't dig the borders. That would be cheating!" << std::endl;
c->con << "I won't dig the borders. That would be cheating!\n";
c->Resume();
return CR_FAILURE;
}
@ -78,12 +78,12 @@ DFhackCExport command_result vdig (Core * c, vector <string> & parameters)
int16_t veinmat = MCache->veinMaterialAt(xy);
if( veinmat == -1 )
{
dfout << "This tile is not a vein." << std::endl;
c->con << "This tile is not a vein.\n";
delete MCache;
c->Resume();
return CR_FAILURE;
}
fprintf(dfout_C,"%d/%d/%d tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, des.whole);
c->con.print("%d/%d/%d tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, des.whole);
stack <DFHack::DFCoord> flood;
flood.push(xy);

@ -58,7 +58,7 @@ DFhackCExport command_result weather (Core * c, vector <string> & parameters)
}
if(lock && unlock)
{
dfout << "Lock or unlock? DECIDE!" << std::endl;
c->con << "Lock or unlock? DECIDE!" << std::endl;
return CR_FAILURE;
}
int cnt = 0;
@ -67,7 +67,7 @@ DFhackCExport command_result weather (Core * c, vector <string> & parameters)
cnt += clear;
if(cnt > 1)
{
dfout << "Rain, snow or clear sky? DECIDE!" << std::endl;
c->con << "Rain, snow or clear sky? DECIDE!" << std::endl;
return CR_FAILURE;
}
bool something = lock || unlock || rain || snow || clear;
@ -75,14 +75,14 @@ DFhackCExport command_result weather (Core * c, vector <string> & parameters)
DFHack::World * w = c->getWorld();
if(!w->wmap)
{
dfout << "Weather support seems broken :(" << std::endl;
c->con << "Weather support seems broken :(" << std::endl;
c->Resume();
return CR_FAILURE;
}
if(!something)
{
// paint weather map
dfout << "Weather map (C = clear, R = rain, S = snow):" << std::endl;
c->con << "Weather map (C = clear, R = rain, S = snow):" << std::endl;
for(int y = 0; y<5;y++)
{
for(int x = 0; x<5;x++)
@ -90,20 +90,20 @@ DFhackCExport command_result weather (Core * c, vector <string> & parameters)
switch((*w->wmap)[x][y])
{
case DFHack::CLEAR:
dfout << "C ";
c->con << "C ";
break;
case DFHack::RAINING:
dfout << "R ";
c->con << "R ";
break;
case DFHack::SNOWING:
dfout << "S ";
c->con << "S ";
break;
default:
dfout << (int) (*w->wmap)[x][y] << " ";
c->con << (int) (*w->wmap)[x][y] << " ";
break;
}
}
dfout << std::endl;
c->con << std::endl;
}
}
else
@ -111,17 +111,17 @@ DFhackCExport command_result weather (Core * c, vector <string> & parameters)
// weather changing action!
if(rain)
{
dfout << "Here comes the rain." << std::endl;
c->con << "Here comes the rain." << std::endl;
w->SetCurrentWeather(RAINING);
}
if(snow)
{
dfout << "Snow everywhere!" << std::endl;
c->con << "Snow everywhere!" << std::endl;
w->SetCurrentWeather(SNOWING);
}
if(clear)
{
dfout << "Suddenly, sunny weather!" << std::endl;
c->con << "Suddenly, sunny weather!" << std::endl;
w->SetCurrentWeather(CLEAR);
}
// FIXME: weather lock needs map ID to work reliably... needs to be implemented.