diff --git a/library/Console-linux.cpp b/library/Console-linux.cpp index e9dce679a..d599402a9 100644 --- a/library/Console-linux.cpp +++ b/library/Console-linux.cpp @@ -140,6 +140,23 @@ namespace DFHack { //sync(); } + private: + bool read_char(unsigned char & out) + { + while(1) + { + if (select(FD_SETSIZE, &descriptor_set, NULL, NULL, NULL) < 0) + return false; + if (FD_ISSET(STDIN_FILENO, &descriptor_set)) + { + // read byte from stdin + read(STDIN_FILENO, &out, 1); + return true; + } + if (FD_ISSET(exit_pipe[0], &descriptor_set)) + return false; + } + } protected: int sync() { @@ -267,7 +284,7 @@ namespace DFHack /// beep. maybe? //void beep (void); /// A simple line edit (raw mode) - int lineedit(const std::string& prompt, std::string& output, mutex * lock) + int lineedit(const std::string& prompt, std::string& output, mutex * lock, CommandHistory & ch) { output.clear(); this->prompt = prompt; @@ -288,7 +305,7 @@ namespace DFHack if(state == con_lineedit) return -1; state = con_lineedit; - count = prompt_loop(lock); + count = prompt_loop(lock,ch); state = con_unclaimed; disable_raw(); print("\n"); @@ -299,19 +316,6 @@ namespace DFHack return count; } } - /// add a command to the history - void history_add(const std::string& command) - { - // if current command = last in history -> do not add. Always add if history is empty. - if(!history.empty() && history.front() == command) - return; - history.push_front(command); - if(history.size() > 100) - history.pop_back(); - } - /// clear the command history - void history_clear(); - int enable_raw() { struct termios raw; @@ -381,7 +385,7 @@ namespace DFHack if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; } - int prompt_loop(mutex * lock) + int prompt_loop(mutex * lock, CommandHistory & history) { int fd = STDIN_FILENO; size_t plen = prompt.size(); @@ -391,18 +395,20 @@ namespace DFHack /* The latest history entry is always our current buffer, that * initially is just an empty string. */ const std::string empty; - history_add(empty); + history.add(empty); if (::write(fd,prompt.c_str(),prompt.size()) == -1) return -1; while(1) { - char c; - int nread; - char seq[2], seq2; + unsigned char c; + int isok; + unsigned char seq[2], seq2; lock->unlock(); - nread = ::read(fd,&c,1); + if(!read_char(c)) + { + lock->lock(); + return -2; + } lock->lock(); - if (nread <= 0) return raw_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. */ @@ -429,7 +435,7 @@ namespace DFHack switch(c) { case 13: // enter - history.pop_front(); + history.remove(); return raw_buffer.size(); case 3: // ctrl-c errno = EAGAIN; @@ -445,10 +451,10 @@ namespace DFHack break; case 27: // escape sequence lock->unlock(); - if (::read(fd,seq,2) == -1) + if(!read_char(seq[0]) || !read_char(seq[1])) { lock->lock(); - break; + return -2; } lock->lock(); if(seq[0] == '[') @@ -513,10 +519,10 @@ namespace DFHack { // extended escape lock->unlock(); - if (::read(fd,&seq2,1) == -1) + if(!read_char(seq2)) { lock->lock(); - return -1; + return -2; } lock->lock(); if (seq[1] == '3' && seq2 == '~' ) @@ -579,7 +585,6 @@ namespace DFHack return raw_buffer.size(); } FILE * dfout_C; - std::deque history; bool supported_terminal; // state variables bool rawmode; // is raw mode active? @@ -593,6 +598,9 @@ namespace DFHack std::string prompt; // current prompt string std::string raw_buffer; // current raw mode buffer int raw_cursor; // cursor position in the buffer + // thread exit mechanism + int exit_pipe[2]; + fd_set descriptor_set; }; } @@ -629,6 +637,11 @@ bool Console::init(bool sharing) std::cin.tie(this); clear(); d->supported_terminal = !isUnsupportedTerm() && isatty(STDIN_FILENO); + // init the exit mechanism + pipe(d->exit_pipe); + FD_ZERO(&d->descriptor_set); + FD_SET(STDIN_FILENO, &d->descriptor_set); + FD_SET(d->exit_pipe[0], &d->descriptor_set); inited = true; } @@ -641,6 +654,8 @@ bool Console::shutdown(void) d->disable_raw(); d->print("\n"); inited = false; + // kill the thing + close(d->exit_pipe[1]); return true; } @@ -727,20 +742,12 @@ void Console::cursor(bool enable) d->cursor(enable); } -// push to front, remove from back if we are above maximum. ignore immediate duplicates -void Console::history_add(const std::string & command) -{ - lock_guard g(*wlock); - if(inited) - d->history_add(command); -} - -int Console::lineedit(const std::string & prompt, std::string & output) +int Console::lineedit(const std::string & prompt, std::string & output, CommandHistory & ch) { lock_guard g(*wlock); int ret = -2; if(inited) - ret = d->lineedit(prompt,output,wlock); + ret = d->lineedit(prompt,output,wlock,ch); return ret; } diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp index 681b2c689..92cfae5a6 100644 --- a/library/Console-windows.cpp +++ b/library/Console-windows.cpp @@ -250,7 +250,7 @@ namespace DFHack SetConsoleCursorPosition(console_out, inf.dwCursorPosition); } - int prompt_loop(mutex * lock) + int prompt_loop(mutex * lock, CommandHistory & history) { raw_buffer.clear(); // make sure the buffer is empty! size_t plen = prompt.size(); @@ -259,7 +259,7 @@ namespace DFHack // The latest history entry is always our current buffer, that // initially is just an empty string. const std::string empty; - history_add(empty); + history.add(empty); CONSOLE_SCREEN_BUFFER_INFO inf = { 0 }; GetConsoleScreenBufferInfo(console_out, &inf); @@ -280,7 +280,7 @@ namespace DFHack switch (rec.Event.KeyEvent.wVirtualKeyCode) { case VK_RETURN: // enter - history.pop_front(); + history.remove(); return raw_buffer.size(); case VK_BACK: // backspace if (raw_cursor > 0 && raw_buffer.size() > 0) @@ -359,13 +359,13 @@ namespace DFHack } } } - int lineedit(const std::string & prompt, std::string & output, mutex * lock) + int lineedit(const std::string & prompt, std::string & output, mutex * lock, CommandHistory & ch) { output.clear(); int count; state = con_lineedit; this->prompt = prompt; - count = prompt_loop(lock); + count = prompt_loop(lock, ch); if(count != -1) output = raw_buffer; state = con_unclaimed; @@ -373,21 +373,8 @@ namespace DFHack return count; } - // push to front, remove from back if we are above maximum. ignore immediate duplicates - void history_add(const std::string & command) - { - // if current command = last in history -> do not add. Always add if history is empty. - if(!history.empty() && history.front() == command) - return; - history.push_front(command); - if(history.size() > 100) - history.pop_back(); - } - FILE * dfout_C; int rawmode; - std::deque history; - HANDLE console_in; HANDLE console_out; HWND ConsoleWindow; @@ -556,20 +543,12 @@ void Console::cursor(bool enable) d->cursor(enable); } -// push to front, remove from back if we are above maximum. ignore immediate duplicates -void Console::history_add(const std::string & command) -{ - lock_guard g(*wlock); - if(inited) - d->history_add(command); -} - -int Console::lineedit(const std::string & prompt, std::string & output) +int Console::lineedit(const std::string & prompt, std::string & output, CommandHistory & ch) { wlock->lock(); int ret = -2; if(inited) - ret = d->lineedit(prompt,output,wlock); + ret = d->lineedit(prompt,output,wlock,ch); wlock->unlock(); return ret; } diff --git a/library/Core.cpp b/library/Core.cpp index 7a871eea7..f07be9338 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -133,6 +133,8 @@ void fIOthread(void * iodata) IODATA * iod = ((IODATA*) iodata); Core * core = iod->core; PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr; + CommandHistory main_history; + main_history.load("dfhack.history"); Console & con = core->con; if(plug_mgr == 0 || core == 0) { @@ -145,7 +147,7 @@ void fIOthread(void * iodata) while (true) { string command = ""; - int ret = con.lineedit("[DFHack]# ",command); + int ret = con.lineedit("[DFHack]# ",command, main_history); if(ret == -2) { cerr << "Console is shutting down properly." << endl; @@ -159,7 +161,8 @@ void fIOthread(void * iodata) else if(ret) { // a proper, non-empty command was entered - con.history_add(command); + main_history.add(command); + main_history.save("dfhack.history"); } // cut the input into parts vector parts; @@ -387,7 +390,7 @@ Core::Core() hotkey_set = false; HotkeyMutex = 0; HotkeyCond = 0; - misc_data_mutex=0; + misc_data_mutex=0; }; bool Core::Init() diff --git a/library/include/dfhack/Console.h b/library/include/dfhack/Console.h index 0c43ccd78..0aaccd696 100644 --- a/library/include/dfhack/Console.h +++ b/library/include/dfhack/Console.h @@ -25,7 +25,12 @@ distribution. #pragma once #include "dfhack/Pragma.h" #include "dfhack/Export.h" -#include +#include +#include +#include +#include +#include +#include namespace tthread { class mutex; @@ -34,6 +39,74 @@ namespace tthread } namespace DFHack { + class CommandHistory + { + public: + CommandHistory(std::size_t capacity = 100) + { + this->capacity = capacity; + } + bool load (const char * filename) + { + std::string reader; + std::ifstream infile(filename); + if(infile.bad()) + return false; + std::string s; + while(std::getline(infile, s)) + { + if(s.empty()) + continue; + history.push_back(s); + } + return true; + } + bool save (const char * filename) + { + std::ofstream outfile (filename); + if(outfile.bad()) + return false; + for(auto iter = history.begin();iter < history.end(); iter++) + { + outfile << *iter << std::endl; + } + outfile.close(); + return true; + } + /// add a command to the history + void add(const std::string& command) + { + // if current command = last in history -> do not add. Always add if history is empty. + if(!history.empty() && history.front() == command) + return; + history.push_front(command); + if(history.size() > capacity) + history.pop_back(); + } + /// clear the command history + void clear() + { + history.clear(); + } + /// get current history size + std::size_t size() + { + return history.size(); + } + /// get pointer to a particular history item + std::string & operator[](std::size_t index) + { + assert(index < history.size()); + return history[index]; + } + void remove( void ) + { + history.pop_front(); + } + private: + std::size_t capacity; + std::deque history; + }; class Private; class DFHACK_EXPORT Console : public std::ostream { @@ -91,14 +164,10 @@ namespace DFHack /// 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); - /// clear the command history - void history_clear(); + int lineedit(const std::string& prompt, std::string& output, CommandHistory & history ); private: Private * d; tthread::mutex * wlock; bool inited; }; -} \ No newline at end of file +} diff --git a/plugins/Dfusion/dfusion.cpp b/plugins/Dfusion/dfusion.cpp index 2a013328d..59d879a11 100644 --- a/plugins/Dfusion/dfusion.cpp +++ b/plugins/Dfusion/dfusion.cpp @@ -15,7 +15,7 @@ #include "lua_Console.h" #include "lua_Process.h" #include "lua_Hexsearch.h" -#include "lua_Misc.h" +#include "lua_Misc.h" #include "lua_VersionInfo.h" #include "functioncall.h" @@ -41,7 +41,7 @@ DFhackCExport command_result plugin_init ( Core * c, std::vector lua::RegisterConsole(lua::glua::Get(),&c->con); lua::RegisterProcess(lua::glua::Get(),c->p); lua::RegisterHexsearch(lua::glua::Get()); - lua::RegisterMisc(lua::glua::Get()); + lua::RegisterMisc(lua::glua::Get()); lua::RegisterVersionInfo(lua::glua::Get()); commands.push_back(PluginCommand("dfusion","Init dfusion system.",dfusion)); commands.push_back(PluginCommand("lua", "Run interactive interpreter.\ @@ -89,13 +89,14 @@ DFhackCExport command_result plugin_onupdate ( Core * c ) void InterpreterLoop(Core* c) { Console &con=c->con; + DFHack::CommandHistory hist; lua::state s=lua::glua::Get(); string curline; con.print("Type quit to exit interactive mode\n"); - con.lineedit(">>",curline); + con.lineedit(">>",curline,hist); while (curline!="quit") { - con.history_add(curline); + hist.add(curline); try { s.loadstring(curline); @@ -104,10 +105,10 @@ void InterpreterLoop(Core* c) catch(lua::exception &e) { con.printerr("Error:%s\n",e.what()); - c->con.printerr("%s",lua::DebugDump(lua::glua::Get()).c_str()); + c->con.printerr("%s",lua::DebugDump(lua::glua::Get()).c_str()); s.settop(0); } - con.lineedit(">>",curline); + con.lineedit(">>",curline,hist); } s.settop(0); } @@ -125,7 +126,7 @@ DFhackCExport command_result lua_run (Core * c, vector & parameters) catch(lua::exception &e) { con.printerr("Error:%s\n",e.what()); - c->con.printerr("%s",lua::DebugDump(lua::glua::Get()).c_str()); + c->con.printerr("%s",lua::DebugDump(lua::glua::Get()).c_str()); } } else @@ -150,7 +151,7 @@ DFhackCExport command_result dfusion (Core * c, vector & parameters) catch(lua::exception &e) { con.printerr("Error:%s\n",e.what()); - c->con.printerr("%s",lua::DebugDump(lua::glua::Get()).c_str()); + c->con.printerr("%s",lua::DebugDump(lua::glua::Get()).c_str()); } s.settop(0);// clean up mymutex->unlock(); diff --git a/plugins/Dfusion/src/lua_Console.cpp b/plugins/Dfusion/src/lua_Console.cpp index d0b1a7c66..2e548b0fa 100644 --- a/plugins/Dfusion/src/lua_Console.cpp +++ b/plugins/Dfusion/src/lua_Console.cpp @@ -88,25 +88,12 @@ static int lua_Console_lineedit(lua_State *S) lua::state st(S); DFHack::Console* c=GetConsolePtr(st); string ret; - int i=c->lineedit(st.as(1),ret); + DFHack::CommandHistory hist; + int i=c->lineedit(st.as(1),ret,hist); st.push(ret); st.push(i); return 2;// dunno if len is needed... } -static int lua_Console_history_add(lua_State *S) -{ - lua::state st(S); - DFHack::Console* c=GetConsolePtr(st); - c->history_add(st.as(1)); - return 0; -} -/*static int lua_Console_history_clear(lua_State *S) //TODO someday add this -{ - lua::state st(S); - DFHack::Console* c=GetConsolePtr(st); - c->history_clear(); - return 0; -}*/ const luaL_Reg lua_console_func[]= { {"print",lua_Console_print}, @@ -120,8 +107,6 @@ const luaL_Reg lua_console_func[]= {"get_columns",lua_Console_get_columns}, {"get_rows",lua_Console_get_rows}, {"lineedit",lua_Console_lineedit}, - {"history_add",lua_Console_history_add}, - //{"history_clear",lua_Console_history_clear}, {NULL,NULL} }; void lua::RegisterConsole(lua::state &st, DFHack::Console *c) diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 27035bff9..56e1b69a8 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -140,6 +140,8 @@ public: }; }; +CommandHistory liquids_hist; + DFhackCExport command_result df_liquids (Core * c, vector & parameters); DFhackCExport const char * plugin_name ( void ) @@ -149,6 +151,7 @@ DFhackCExport const char * plugin_name ( void ) DFhackCExport command_result plugin_init ( Core * c, std::vector &commands) { + liquids_hist.load("liquids.history"); commands.clear(); commands.push_back(PluginCommand("liquids", "Place magma, water or obsidian.", df_liquids, true)); return CR_OK; @@ -156,6 +159,7 @@ DFhackCExport command_result plugin_init ( Core * c, std::vector DFhackCExport command_result plugin_shutdown ( Core * c ) { + liquids_hist.save("liquids.history"); return CR_OK; } @@ -192,7 +196,7 @@ DFhackCExport command_result df_liquids (Core * c, vector & parameters) string command = ""; std::stringstream str; str <<"[" << mode << ":" << brushname << ":" << amount << ":" << flowmode << ":" << setmode << "]#"; - if(c->con.lineedit(str.str(),command) == -1) + if(c->con.lineedit(str.str(),command,liquids_hist) == -1) return CR_FAILURE; if(command=="help" || command == "?") { @@ -260,20 +264,24 @@ DFhackCExport command_result df_liquids (Core * c, vector & parameters) else if(command == "range" || command == "r") { std::stringstream str; + CommandHistory range_hist; str << " :set range width<" << width << "># "; - c->con.lineedit(str.str(),command); + c->con.lineedit(str.str(),command,range_hist); + range_hist.add(command); width = command == "" ? width : atoi (command.c_str()); if(width < 1) width = 1; str.clear(); str << " :set range height<" << height << "># "; - c->con.lineedit(str.str(),command); + c->con.lineedit(str.str(),command,range_hist); + range_hist.add(command); height = command == "" ? height : atoi (command.c_str()); if(height < 1) height = 1; str.clear(); str << " :set range z-levels<" << z_levels << "># "; - c->con.lineedit(str.str(),command); + c->con.lineedit(str.str(),command,range_hist); + range_hist.add(command); z_levels = command == "" ? z_levels : atoi (command.c_str()); if(z_levels < 1) z_levels = 1; delete brush; diff --git a/plugins/mode.cpp b/plugins/mode.cpp index 1ae336b93..4a64fd2b8 100644 --- a/plugins/mode.cpp +++ b/plugins/mode.cpp @@ -131,7 +131,8 @@ DFhackCExport command_result mode (Core * c, vector & parameters) string selected; input_again: - c->con.lineedit("Enter new mode: ",selected); + CommandHistory hist; + c->con.lineedit("Enter new mode: ",selected, hist); if(selected == "c") return CR_OK; const char * start = selected.c_str(); diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index b1e4e0243..5fb5f7e72 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -411,6 +411,8 @@ public: }; }; +CommandHistory tiletypes_hist; + DFhackCExport command_result df_tiletypes (Core * c, vector & parameters); DFhackCExport const char * plugin_name ( void ) @@ -420,6 +422,7 @@ DFhackCExport const char * plugin_name ( void ) DFhackCExport command_result plugin_init ( Core * c, std::vector &commands) { + tiletypes_hist.load("tiletypes.history"); commands.clear(); commands.push_back(PluginCommand("tiletypes", "Paint map tiles freely, similar to liquids.", df_tiletypes, true)); return CR_OK; @@ -427,6 +430,7 @@ DFhackCExport command_result plugin_init ( Core * c, std::vector DFhackCExport command_result plugin_shutdown ( Core * c ) { + tiletypes_hist.save("tiletypes.history"); return CR_OK; } @@ -455,7 +459,8 @@ DFhackCExport command_result df_tiletypes (Core * c, vector & parameter std::string option = ""; std::string value = ""; - c->con.lineedit("tiletypes> ",input); + c->con.lineedit("tiletypes> ",input,tiletypes_hist); + tiletypes_hist.add(input); std::istringstream ss(input); ss >> command >> option >> value; tolower(command); @@ -486,20 +491,21 @@ DFhackCExport command_result df_tiletypes (Core * c, vector & parameter else if (command == "range" || command == "r") { std::stringstream ss; + CommandHistory hist; ss << "Set range width <" << width << "> "; - c->con.lineedit(ss.str(),command); + c->con.lineedit(ss.str(),command,hist); width = command == "" ? width : toint(command); if (width < 1) width = 1; ss.str(""); ss << "Set range height <" << height << "> "; - c->con.lineedit(ss.str(),command); + c->con.lineedit(ss.str(),command,hist); height = command == "" ? height : toint(command); if (height < 1) height = 1; ss.str(""); ss << "Set range z-levels <" << z_levels << "> "; - c->con.lineedit(ss.str(),command); + c->con.lineedit(ss.str(),command,hist); z_levels = command == "" ? z_levels : toint(command); if (z_levels < 1) z_levels = 1; diff --git a/plugins/weather.cpp b/plugins/weather.cpp index 76757b257..d01bfcb7b 100644 --- a/plugins/weather.cpp +++ b/plugins/weather.cpp @@ -23,10 +23,7 @@ DFhackCExport const char * plugin_name ( void ) DFhackCExport command_result plugin_init ( Core * c, std::vector &commands) { commands.clear(); - commands.push_back(PluginCommand("weather", - "Print the weather map or change weather.\ -\n Options: 'snow' = make it snow, 'rain' = make it rain.\ -\n 'clear' = clear the sky",weather)); + commands.push_back(PluginCommand("weather", "Print the weather map or change weather.",weather)); return CR_OK; } @@ -61,7 +58,13 @@ DFhackCExport command_result weather (Core * c, vector & parameters) } if(help) { - + c->con.print("Prints the current weather map by default.\n" + "Options:\n" + "snow - make it snow everywhere.\n" + "rain - make it rain.\n" + "clear - clear the sky.\n" + ); + return CR_OK; } if(lock && unlock) {