/***** * runhistory.in * * Runtime functions for history operations. * *****/ pair => primPair() picture* => primPicture() stringarray* => stringArray() #include "array.h" #include "mathop.h" #include "builtin.h" #if defined(_WIN32) #include #define isatty _isatty #else #include #endif using namespace camp; using namespace settings; using namespace vm; using namespace run; typedef array stringarray; using types::stringArray; namespace camp { bool allowRender=false; } #if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES) #include #include struct historyState { bool store; HISTORY_STATE state; historyState() : store(false), state() {} }; typedef mem::map historyMap_t; historyMap_t historyMap; static HISTORY_STATE history_save; // Deep-copy entries so each saved state owns independent memory. // This prevents use-after-free when readline frees the previous // entries array during history_set_history_state. static void deep_copy_entries(HISTORY_STATE *dest) { if(dest->length == 0 || !dest->entries) { dest->entries = NULL; return; } HIST_ENTRY **ne = new HIST_ENTRY *[dest->length]; for(int i = 0; i < dest->length; ++i) { HIST_ENTRY *e = dest->entries[i]; if(e) { HIST_ENTRY *n = new HIST_ENTRY; n->line = e->line ? strdup(e->line) : NULL; n->data = e->data; ne[i] = n; } else ne[i] = NULL; } dest->entries = ne; } // Store a deep copy of the current readline history in dest. void store_history(HISTORY_STATE *dest) { HISTORY_STATE *src=history_get_history_state(); if(src) { *dest=*src; deep_copy_entries(dest); free(src); } } // Set the global history to use entries from a saved state, // without calling history_set_history_state (which would free // our GC-allocated entries with readline's own free). static void set_history(const HISTORY_STATE &state) { clear_history(); for(int i = 0; i < state.length; ++i) { HIST_ENTRY *e = state.entries[i]; if(e && e->line) add_history(e->line); } } // Write the last nlines entries of a saved history state to filename. static void write_history_from_state(const HISTORY_STATE &state, const string &filename, int nlines) { if(state.entries == NULL || state.length == 0) return; int start = Max(0, (int)state.length - nlines); std::ofstream fout(filename.c_str(), std::ios::trunc); for(int i = start; i < (int)state.length; ++i) { if(state.entries[i] && state.entries[i]->line) fout << state.entries[i]->line << "\n"; } } stringarray* get_history(Int n) { int N=intcast(n); if(N <= 0) N=history_length; else N=Min(N,history_length); array *a=new array((size_t) N); int offset=history_length-N+1; for(int i=0; i < N; ++i) { HIST_ENTRY *last=history_get(offset+i); string s=last ? last->line : ""; (*a)[i]=s; } return a; } string historyfilename(const string &name) { return historyname+"_"+name; } #endif namespace run { extern string emptystring; #if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES) #endif void cleanup() { #if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES) store_history(&history_save); int nlines=intcast(getSetting("historylines")); for(historyMap_t::iterator h=historyMap.begin(); h != historyMap.end(); ++h) { if(h->second.store) write_history_from_state(h->second.state, historyfilename(h->first), nlines); } #endif #ifdef HAVE_LIBGSL trans::GSLrngFree(); #endif } } // Autogenerated routines: // Return the last n lines of the history named name. stringarray* history(string name, Int n=1) { #if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES) bool newhistory=historyMap.find(name) == historyMap.end(); string filename; if(newhistory) { filename=historyfilename(name); std::ifstream exists(filename.c_str()); if(!exists) return new array(0); } store_history(&history_save); HISTORY_STATE& history=historyMap[name].state; set_history(history); if(newhistory) read_history(filename.c_str()); array *a=get_history(n); store_history(&history); set_history(history_save); return a; #else unused(&n); return new array(0); #endif } // Return the last n lines of the interactive history. stringarray* history(Int n=0) { #if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES) return get_history(n); #else unused(&n); return new array(0); #endif } // Prompt for a string using prompt, the GNU readline library, and a // local history named name. string readline(string prompt=emptystring, string name=emptystring, bool tabcompletion=false) { bool stdinIsTty=isatty(STDIN_FILENO); bool hasInpipe=getSetting("inpipe") >= 0; if(!(stdinIsTty || hasInpipe)) { return emptystring; } #if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES) interact::init_readline(tabcompletion); store_history(&history_save); bool newhistory=historyMap.find(name) == historyMap.end(); historyState& h=historyMap[name]; HISTORY_STATE& history=h.state; set_history(history); if(newhistory) read_history(historyfilename(name).c_str()); static char *line=NULL; /* Return the memory to the free pool if the buffer has already been allocated. */ if(line) { free(line); line=NULL; } /* Get a line from the user. */ bool allowRenderSave=allowRender; allowRender=false; line=readline(prompt.c_str()); allowRender=allowRenderSave; if(!line) cout << endl; set_history(history_save); return line ? string(line) : emptystring; #else cout << prompt; string s; getline(cin,s); unused(&tabcompletion); // Avoid unused variable warning message. return s; #endif } // Save a string in a local history named name. // If store=true, store the local history in the file historyfilename(name). void saveline(string name, string value, bool store=true) { #if defined(HAVE_LIBREADLINE) && defined(HAVE_LIBCURSES) store_history(&history_save); bool newhistory=historyMap.find(name) == historyMap.end(); historyState& h=historyMap[name]; h.store=store; HISTORY_STATE& history=h.state; set_history(history); if(newhistory) read_history(historyfilename(name).c_str()); if(value != "") { add_history(value.c_str()); if(store) { std::ofstream hout(historyfilename(name).c_str(),std::ios::app); hout << value << endl; } } store_history(&history); set_history(history_save); #else unused(&store); #endif }