diff --git a/fdbcli/FlowLineNoise.actor.cpp b/fdbcli/FlowLineNoise.actor.cpp index 85ae2c0bfb..6c101ca666 100644 --- a/fdbcli/FlowLineNoise.actor.cpp +++ b/fdbcli/FlowLineNoise.actor.cpp @@ -113,7 +113,7 @@ LineNoise::LineNoise( for( auto const& c : completions ) linenoiseAddCompletion( lc, c.c_str() ); }); - /*linenoiseSetHintsCallback( [](const char* line, int* color, int*bold) -> const char* { + linenoiseSetHintsCallback( [](const char* line, int* color, int*bold) -> char* { Hint h = onMainThread( [line]() -> Future { return hint_callback(line); }).getBlocking(); @@ -122,7 +122,7 @@ LineNoise::LineNoise( *bold = h.bold; return strdup( h.text.c_str() ); }); - linenoiseSetFreeHintsCallback( free );*/ + linenoiseSetFreeHintsCallback( free ); #endif threadPool->addThread(reader); diff --git a/fdbcli/fdbcli.actor.cpp b/fdbcli/fdbcli.actor.cpp index bf78a4ece5..de69038a6a 100644 --- a/fdbcli/fdbcli.actor.cpp +++ b/fdbcli/fdbcli.actor.cpp @@ -67,6 +67,7 @@ enum { OPT_TIMEOUT, OPT_EXEC, OPT_NO_STATUS, + OPT_NO_HINTS, OPT_STATUS_FROM_JSON, OPT_VERSION, OPT_TRACE_FORMAT, @@ -81,6 +82,7 @@ CSimpleOpt::SOption g_rgOptions[] = { { OPT_CONNFILE, "-C", SO_REQ_SEP }, { OPT_TIMEOUT, "--timeout", SO_REQ_SEP }, { OPT_EXEC, "--exec", SO_REQ_SEP }, { OPT_NO_STATUS, "--no-status", SO_NONE }, + { OPT_NO_HINTS, "--no-hints", SO_NONE }, { OPT_HELP, "-?", SO_NONE }, { OPT_HELP, "-h", SO_NONE }, { OPT_HELP, "--help", SO_NONE }, @@ -493,7 +495,7 @@ void initHelp() { "change the class of a process", "If no address and class are specified, lists the classes of all servers.\n\nSetting the class to `default' resets the process class to the class specified on the command line."); helpMap["status"] = CommandHelp( - "status [minimal] [details] [json]", + "status [minimal|details|json]", "get the status of a FoundationDB cluster", "If the cluster is down, this command will print a diagnostic which may be useful in figuring out what is wrong. If the cluster is running, this command will print cluster statistics.\n\nSpecifying 'minimal' will provide a minimal description of the status of your database.\n\nSpecifying 'details' will provide load information for individual workers.\n\nSpecifying 'json' will provide status information in a machine readable JSON format."); helpMap["exit"] = CommandHelp("exit", "exit the CLI", ""); @@ -544,7 +546,7 @@ void initHelp() { "attempts to kill one or more processes in the cluster", "If no addresses are specified, populates the list of processes which can be killed. Processes cannot be killed before this list has been populated.\n\nIf `all' is specified, attempts to kill all known processes.\n\nIf `list' is specified, displays all known processes. This is only useful when the database is unresponsive.\n\nFor each IP:port pair in
*, attempt to kill the specified process."); helpMap["profile"] = CommandHelp( - " ", + "profile ", "namespace for all the profiling-related commands.", "Different types support different actions. Run `profile` to get a list of types, and iteratively explore the help.\n"); helpMap["force_recovery_with_data_loss"] = CommandHelp( @@ -2466,17 +2468,18 @@ void LogCommand(std::string line, UID randomID, std::string errMsg) { struct CLIOptions { std::string program_name; - int exit_code; + int exit_code = -1; std::string commandLine; std::string clusterFile; - bool trace; + bool trace = false; std::string traceDir; std::string traceFormat; - int exit_timeout; + int exit_timeout = 0; Optional exec; - bool initialStatusCheck; + bool initialStatusCheck = true; + bool cliHints = true; std::string tlsCertPath; std::string tlsKeyPath; std::string tlsVerifyPeers; @@ -2486,10 +2489,6 @@ struct CLIOptions { std::vector> knobs; CLIOptions( int argc, char* argv[] ) - : trace(false), - exit_timeout(0), - initialStatusCheck(true), - exit_code(-1) { program_name = argv[0]; for (int a = 0; a runCli(CLIOptions opt) { [](std::string const& line, std::vector& completions) { fdbcli_comp_cmd(line, completions); }, - [](std::string const& line)->LineNoise::Hint { - return LineNoise::Hint(); + [enabled=opt.cliHints](std::string const& line)->LineNoise::Hint { + if (!enabled) { + return LineNoise::Hint(); + } + + bool error = false; + bool partial = false; + std::string linecopy = line; + std::vector> parsed = parseLine(linecopy, error, partial); + if (parsed.size() == 0 || parsed.back().size() == 0) return LineNoise::Hint(); + StringRef command = parsed.back().front(); + int finishedParameters = parsed.back().size() + error; + + // As a user is typing an escaped character, e.g. \", after the \ and before the " is typed + // the string will be a parse error. Ignore this parse error to avoid flipping the hint to + // {malformed escape sequence} and back to the original hint for the span of one character + // being entered. + if (error && line.back() != '\\') return LineNoise::Hint(std::string(" {malformed escape sequence}"), 90, false); + + auto iter = helpMap.find(command.toString()); + if (iter != helpMap.end()) { + std::string helpLine = iter->second.usage; + std::vector> parsedHelp = parseLine(helpLine, error, partial); + std::string hintLine = (*(line.end() - 1) == ' ' ? "" : " "); + for (int i = finishedParameters; i < parsedHelp.back().size(); i++) { + hintLine = hintLine + parsedHelp.back()[i].toString() + " "; + } + return LineNoise::Hint(hintLine, 90, false); + } else { + return LineNoise::Hint(); + } }, 1000, false); diff --git a/fdbcli/linenoise/linenoise.c b/fdbcli/linenoise/linenoise.c index 30d64ececf..10ffd71c35 100644 --- a/fdbcli/linenoise/linenoise.c +++ b/fdbcli/linenoise/linenoise.c @@ -111,6 +111,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,8 @@ #define LINENOISE_MAX_LINE 4096 static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; static linenoiseCompletionCallback *completionCallback = NULL; +static linenoiseHintsCallback *hintsCallback = NULL; +static linenoiseFreeHintsCallback *freeHintsCallback = NULL; static struct termios orig_termios; /* In order to restore at exit.*/ static int rawmode = 0; /* For atexit() function to check if restore is needed*/ @@ -407,6 +410,18 @@ void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { completionCallback = fn; } +/* Register a hits function to be called to show hits to the user at the + * right of the prompt. */ +void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { + hintsCallback = fn; +} + +/* Register a function to free the hints returned by the hints callback + * registered with linenoiseSetHintsCallback(). */ +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { + freeHintsCallback = fn; +} + /* This function is used by the callback function registered by the user * in order to add completion options given the input string when the * user typed . See the example.c source code for a very easy to @@ -456,6 +471,32 @@ static void abFree(struct abuf *ab) { free(ab->b); } +/* Helper of refreshSingleLine() and refreshMultiLine() to show hints + * to the right of the prompt. */ +void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { + char seq[64]; + if (hintsCallback && plen+l->len < l->cols) { + int color = -1, bold = 0; + char *hint = hintsCallback(l->buf,&color,&bold); + if (hint) { + int hintlen = strlen(hint); + int hintmaxlen = l->cols-(plen+l->len); + if (hintlen > hintmaxlen) hintlen = hintmaxlen; + if (bold == 1 && color == -1) color = 37; + if (color != -1 || bold != 0) + snprintf(seq,64,"\033[%d;%d;49m",bold,color); + else + seq[0] = '\0'; + abAppend(ab,seq,strlen(seq)); + abAppend(ab,hint,hintlen); + if (color != -1 || bold != 0) + abAppend(ab,"\033[0m",4); + /* Call the function to free the hint returned. */ + if (freeHintsCallback) freeHintsCallback(hint); + } + } +} + /* Single line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, @@ -485,6 +526,8 @@ static void refreshSingleLine(struct linenoiseState *l) { /* Write the prompt and the current buffer content */ abAppend(&ab,l->prompt,strlen(l->prompt)); abAppend(&ab,buf,len); + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); /* Erase to right */ snprintf(seq,64,"\x1b[0K"); abAppend(&ab,seq,strlen(seq)); @@ -538,6 +581,9 @@ static void refreshMultiLine(struct linenoiseState *l) { abAppend(&ab,l->prompt,strlen(l->prompt)); abAppend(&ab,l->buf,l->len); + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); + /* If we are at the very end of the screen with our prompt, we need to * emit a newline and move the prompt to the first column. */ if (l->pos && @@ -598,7 +644,7 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) { l->pos++; l->len++; l->buf[l->len] = '\0'; - if ((!mlmode && l->plen+l->len < l->cols) /* || mlmode */) { + if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { /* Avoid a full update of the line in the * trivial case. */ if (write(l->ofd,&c,1) == -1) return -1; @@ -772,6 +818,14 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, history_len--; free(history[history_len]); if (mlmode) linenoiseEditMoveEnd(&l); + if (hintsCallback) { + /* Force a refresh without hints to leave the previous + * line as the user typed it after a newline. */ + linenoiseHintsCallback *hc = hintsCallback; + hintsCallback = NULL; + refreshLine(&l); + hintsCallback = hc; + } return (int)l.len; case CTRL_C: /* ctrl-c */ errno = EAGAIN; @@ -1010,6 +1064,14 @@ char *linenoise(const char *prompt) { } } +/* This is just a wrapper the user may want to call in order to make sure + * the linenoise returned buffer is freed with the same allocator it was + * created with. Useful when the main program is using an alternative + * allocator. */ +void linenoiseFree(void *ptr) { + free(ptr); +} + /* ================================ History ================================= */ /* Free the history, but does not reset it. Only used when we have to @@ -1101,10 +1163,14 @@ int linenoiseHistorySetMaxLen(int len) { /* Save the history in the specified file. On success 0 is returned * otherwise -1 is returned. */ int linenoiseHistorySave(const char *filename) { - FILE *fp = fopen(filename,"w"); + mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); + FILE *fp; int j; + fp = fopen(filename,"w"); + umask(old_umask); if (fp == NULL) return -1; + chmod(filename,S_IRUSR|S_IWUSR); for (j = 0; j < history_len; j++) fprintf(fp,"%s\n",history[j]); fclose(fp); diff --git a/fdbcli/linenoise/linenoise.h b/fdbcli/linenoise/linenoise.h index fbb01cfaad..c388e25a4f 100644 --- a/fdbcli/linenoise/linenoise.h +++ b/fdbcli/linenoise/linenoise.h @@ -39,6 +39,8 @@ #ifndef __LINENOISE_H #define __LINENOISE_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -49,10 +51,15 @@ typedef struct linenoiseCompletions { } linenoiseCompletions; typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); +typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); +typedef void(linenoiseFreeHintsCallback)(void *); void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); +void linenoiseSetHintsCallback(linenoiseHintsCallback *); +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); void linenoiseAddCompletion(linenoiseCompletions *, const char *); char *linenoise(const char *prompt); +void linenoiseFree(void *ptr); int linenoiseHistoryAdd(const char *line); int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySave(const char *filename);