#include #include #include #include #include #include #include #include #include "TclUtils/TclPrompt.hh" extern "C" { // We need to give a bunch or readline-related definitions here. // Regrettably, the framework is still using an ancient version // of readline which uses () to declare functions with arbitrary // arguments. Of course, C++ parses () as void. This is why we // can't include normal readline headers. typedef char *CPFunction (char *, int); typedef char **CPPFunction (char *, int, int); char * readline(char *); void add_history(char *); char **completion_matches(char *, CPFunction *); extern char *rl_line_buffer; extern char *rl_readline_name; extern char *rl_basic_word_break_characters; extern CPPFunction *rl_attempted_completion_function; // The command completion table static char **completion_table = 0; static int completion_table_len = 0; static int completion_table_size = 0; static char *command_generator(char *text, int state) { static int table_index = 0, len = 0; if (!state) { table_index = 0; len = strlen(text); } for ( ; table_index < completion_table_len; table_index++) if (strncmp(completion_table[table_index], text, len) == 0) return strdup(completion_table[table_index++]); return ((char *)0); } static char **mycompleter(char *text, int start, int end) { char **matches = (char **)NULL; int i, iscommand = 0; /* If the word contains :: (namespace delimiter) then * this is probably a command. */ for (i=start; i+1 0) if (!isspace(rl_line_buffer[--start])) break; if (start == 0 || rl_line_buffer[start] == '[') iscommand = 1; } if (iscommand) matches = completion_matches(text, command_generator); return matches; } static void add_completion(const char *entry) { int table_index = 0; if (!entry) return; if (!*entry) return; for (table_index = 0; table_index < completion_table_len; table_index++) if (strcmp(completion_table[table_index], entry) == 0) return; ++completion_table_len; if (completion_table_len > completion_table_size) { completion_table_size += (completion_table_size/2 + 10); completion_table = (char **)realloc( completion_table, completion_table_size*sizeof(char *)); if (completion_table == NULL) { fprintf(stderr, "Fatal error in add_completion: out of memory\n"); fflush(stderr); abort(); } } completion_table[table_index] = strdup(entry); if (completion_table[table_index] == 0) { fprintf(stderr, "Fatal error in add_completion: out of memory\n"); fflush(stderr); abort(); } } static void clear_completion_table(void) { int i = 0; if (completion_table_size > 0) { for (i=0; i<;|&{(["; TclPrompt::TclPrompt(Tcl_Interp *interp, const char * const p1, const char * const p2) { assert(interp); _prompt1 = new char[strlen(p1)+1]; strcpy(_prompt1, p1); _prompt2 = new char[strlen(p2)+1]; strcpy(_prompt2, p2); _status = EOI; // Figure out if we are using interactive mode int tty; char *mode = Tcl_GetVar(interp, "tcl_interactive", TCL_GLOBAL_ONLY); if (mode == 0) tty = isatty(0); else if (Tcl_GetBoolean(interp, mode, &tty) != TCL_OK) tty = isatty(0); if (tty) { // Basic readline initialization rl_readline_name = _readline_name; rl_basic_word_break_characters = _word_breaking_chars; rl_attempted_completion_function = (CPPFunction *)mycompleter; } // Process commands from stdin until the end-of-file. // Try to catch the "exit" command so that we don't get // killed by it. Tcl_Channel inChannel, outChannel, errChannel; inChannel = Tcl_GetStdChannel(TCL_STDIN); outChannel = Tcl_GetStdChannel(TCL_STDOUT); errChannel = Tcl_GetStdChannel(TCL_STDERR); Tcl_DString command; Tcl_DStringInit(&command); bool gotPartial = false; while (inChannel) { if (tty) { // Reinitialize readline completer if necessary. It should be safe to // use "readline" because the framework also uses it. Effectively, this // means that stdin is line-buffered. if (interp != _lastReadlineInterp) { _lastReadlineInterp = interp; // Regenerate the completion table for this interpreter. // First, insert the interpreter commands. clear_completion_table(); char infocmd[] = "info commands"; Tcl_Eval(interp, infocmd); #if (TCL_MAJOR_VERSION > 7) char *cmdlist = Tcl_GetStringResult(interp); #else char *cmdlist = interp->result; #endif Tcl_Interp *ip = Tcl_CreateInterp(); int listlen; char **listelem = 0; Tcl_SplitList(ip, cmdlist, &listlen, &listelem); for (int i=0; i(listelem)); Tcl_DeleteInterp(ip); Tcl_ResetResult(interp); // Now, go over user's PATH and insert all executables char *getpath = getenv("PATH"); if (getpath) { std::string tmp(getpath); char *path = const_cast(tmp.c_str()); for (char *dirname = strtok(path, ":"); dirname; dirname = strtok(0, ":")) { DIR *dir = opendir(dirname); if (dir) { struct dirent *ent; struct stat statbuf; while ((ent = readdir(dir))) { if (ent->d_name[0] != '.') { std::string dirs(dirname); dirs += '/'; dirs += ent->d_name; // We need to check that this is a normal // file and that it is executable if (lstat(dirs.c_str(), &statbuf) == 0) if (S_ISREG(statbuf.st_mode)) if (access(dirs.c_str(), X_OK) == 0) add_completion(ent->d_name); } } closedir(dir); } } } } // Print the prompt and get the user input char *prompt = gotPartial ? _prompt2 : _prompt1; char *line = get_next_line(prompt); if (line) { if (*line) { add_history(line); Tcl_DStringAppend(&command, line, -1); } } else break; } else { // Note that now we should not use std::cin to fetch the input. // This is because the cin buffer may still have some input left. // Tcl I/O uses its own buffering, independent from C or C++. int length = Tcl_Gets(inChannel, &command); if (length < 0) { break; } if ((length == 0) && Tcl_Eof(inChannel) && (!gotPartial)) { break; } } // Add the newline removed by Tcl_Gets back to the string Tcl_DStringAppend(&command, "\n", -1); char *cmd = Tcl_DStringValue(&command); // Try to catch "quit", "exit", or "stop". Of course, // this attempt is lame and it is in odds with Tcl grammar. // But we are not using "normal" Tcl anyway. if (!gotPartial) { char *pc = cmd; for (; isspace(*pc) && *pc; ++pc); if (strncmp("exit", pc, 4) == 0) { pc += 4; if (isspace(*pc) || *pc == '\0' || *pc == ';') { _status = EXIT; break; } } else if (strncmp("quit", pc, 4) == 0) { pc += 4; if (isspace(*pc) || *pc == '\0' || *pc == ';') { _status = QUIT; break; } } else if (strncmp("stop", pc, 4) == 0) { pc += 4; if (isspace(*pc) || *pc == '\0' || *pc == ';') { _status = STOP; break; } } } if (Tcl_CommandComplete(cmd)) { gotPartial = false; int code; if (tty) code = Tcl_RecordAndEval(interp, cmd, 0); else code = Tcl_Eval(interp, cmd); Tcl_DStringFree(&command); // Must get the channels again since they may be // redirected as a result of Tcl_Eval. inChannel = Tcl_GetStdChannel(TCL_STDIN); outChannel = Tcl_GetStdChannel(TCL_STDOUT); errChannel = Tcl_GetStdChannel(TCL_STDERR); if (code != TCL_OK) { if (errChannel) { #if (TCL_MAJOR_VERSION > 7) Tcl_Write(errChannel, Tcl_GetStringResult(interp), -1); #else Tcl_Write(errChannel, interp->result, -1); #endif Tcl_Write(errChannel, "\n", 1); Tcl_Flush(errChannel); } } else if (tty && (*interp->result != 0)) { if (outChannel) { #if (TCL_MAJOR_VERSION > 7) Tcl_Write(outChannel, Tcl_GetStringResult(interp), -1); #else Tcl_Write(outChannel, interp->result, -1); #endif Tcl_Write(outChannel, "\n", 1); Tcl_Flush(outChannel); } } } else { gotPartial = true; } } Tcl_DStringFree(&command); Tcl_ResetResult(interp); } TclPrompt::~TclPrompt() { delete [] _prompt1; delete [] _prompt2; } int TclPrompt::status() { return _status; }