diff --git a/changelog.md b/changelog.md index 569134eae14a8..e48e38dd6dca0 100644 --- a/changelog.md +++ b/changelog.md @@ -138,6 +138,8 @@ with other backends. see #9125. Use `-d:nimLegacyJsRound` for previous behavior. - Added `random.initRand()` overload with no argument which uses the current time as a seed. +- Added experimental `linenoise.readLineStatus` to get line and status (e.g. ctrl-D or ctrl-C). + ## Language changes - `nimscript` now handles `except Exception as e`. diff --git a/lib/impure/rdstdin.nim b/lib/impure/rdstdin.nim index 5f5ffc02f779b..964e0f65fae73 100644 --- a/lib/impure/rdstdin.nim +++ b/lib/impure/rdstdin.nim @@ -12,16 +12,18 @@ ## (e.g. you can navigate with the arrow keys). On Windows ``system.readLine`` ## is used. This suffices because Windows' console already provides the ## wanted functionality. -## -## **Examples:** -## -## .. code-block:: nim -## echo readLineFromStdin("Is Nim awesome? (Y/n):") -## var userResponse: string -## doAssert readLineFromStdin("How are you?:", line = userResponse) -## echo userResponse -when defined(Windows): +runnableExamples: + if false: + echo readLineFromStdin("Is Nim awesome? (Y/n): ") + var line: string + while true: + let ok = readLineFromStdin("How are you? ", line) + if not ok: break # ctrl-C or ctrl-D will cause a break + if line.len > 0: echo line + echo "exiting" + +when defined(windows): proc readLineFromStdin*(prompt: string): string {. tags: [ReadIOEffect, WriteIOEffect].} = ## Reads a line from stdin. diff --git a/lib/wrappers/linenoise/linenoise.c b/lib/wrappers/linenoise/linenoise.c index ae185fece9452..be792b96b035d 100644 --- a/lib/wrappers/linenoise/linenoise.c +++ b/lib/wrappers/linenoise/linenoise.c @@ -765,7 +765,7 @@ void linenoiseEditDeletePrevWord(struct linenoiseState *l) { * when ctrl+d is typed. * * The function returns the length of the current buffer. */ -static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) +static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt, linenoiseData* data) { struct linenoiseState l; @@ -827,6 +827,7 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, return (int)l.len; case CTRL_C: /* ctrl-c */ errno = EAGAIN; + data->status = linenoiseStatus_ctrl_C; return -1; case BACKSPACE: /* backspace */ case 8: /* ctrl-h */ @@ -839,6 +840,7 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, } else { history_len--; free(history[history_len]); + data->status = linenoiseStatus_ctrl_D; return -1; } break; @@ -979,7 +981,7 @@ void linenoisePrintKeyCodes(void) { /* This function calls the line editing function linenoiseEdit() using * the STDIN file descriptor set in raw mode. */ -static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { +static int linenoiseRaw(char *buf, size_t buflen, const char *prompt, linenoiseData* data) { int count; if (buflen == 0) { @@ -988,7 +990,7 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { } if (enableRawMode(STDIN_FILENO) == -1) return -1; - count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); + count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt, data); disableRawMode(STDIN_FILENO); printf("\n"); return count; @@ -1035,7 +1037,7 @@ static char *linenoiseNoTTY(void) { * for a blacklist of stupid terminals, and later either calls the line * editing function or uses dummy fgets() so that you will be able to type * something even in the most desperate of the conditions. */ -char *linenoise(const char *prompt) { +char *linenoiseExtra(const char *prompt, linenoiseData* data) { char buf[LINENOISE_MAX_LINE]; int count; @@ -1056,12 +1058,18 @@ char *linenoise(const char *prompt) { } return strdup(buf); } else { - count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); + count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt, data); if (count == -1) return NULL; return strdup(buf); } } +char *linenoise(const char *prompt) { + linenoiseData data; + data.status = linenoiseStatus_ctrl_unknown; + return linenoiseExtra(prompt, &data); +} + /* 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 diff --git a/lib/wrappers/linenoise/linenoise.h b/lib/wrappers/linenoise/linenoise.h index ed20232c576e2..aa86ccb78a696 100644 --- a/lib/wrappers/linenoise/linenoise.h +++ b/lib/wrappers/linenoise/linenoise.h @@ -48,6 +48,16 @@ typedef struct linenoiseCompletions { char **cvec; } linenoiseCompletions; +typedef enum linenoiseStatus { + linenoiseStatus_ctrl_unknown, + linenoiseStatus_ctrl_C, + linenoiseStatus_ctrl_D +} linenoiseStatus; + +typedef struct linenoiseData { + linenoiseStatus status; +} linenoiseData; + typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); typedef void(linenoiseFreeHintsCallback)(void *); @@ -57,6 +67,7 @@ void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); void linenoiseAddCompletion(linenoiseCompletions *, const char *); char *linenoise(const char *prompt); +char *linenoiseExtra(const char *prompt, linenoiseData* data); void linenoiseFree(void *ptr); int linenoiseHistoryAdd(const char *line); int linenoiseHistorySetMaxLen(int len); diff --git a/lib/wrappers/linenoise/linenoise.nim b/lib/wrappers/linenoise/linenoise.nim index a6260eb128419..3bfd74586d3a6 100644 --- a/lib/wrappers/linenoise/linenoise.nim +++ b/lib/wrappers/linenoise/linenoise.nim @@ -9,7 +9,7 @@ type Completions* = object - len*: csize + len*: csize_t cvec*: cstringArray CompletionCallback* = proc (a2: cstring; a3: ptr Completions) {.cdecl.} @@ -32,3 +32,42 @@ proc printKeyCodes*() {.importc: "linenoisePrintKeyCodes".} proc free*(s: cstring) {.importc: "free", header: "".} +when defined nimExperimentalLinenoiseExtra: + # C interface + type linenoiseStatus = enum + linenoiseStatus_ctrl_unknown + linenoiseStatus_ctrl_C + linenoiseStatus_ctrl_D + + type linenoiseData* = object + status: linenoiseStatus + + proc linenoiseExtra(prompt: cstring, data: ptr linenoiseData): cstring {.importc.} + + # stable nim interface + type Status* = enum + lnCtrlUnkown + lnCtrlC + lnCtrlD + + type ReadLineResult* = object + line*: string + status*: Status + + proc readLineStatus*(prompt: string, result: var ReadLineResult) = + ## line editing API that allows returning the line entered and an indicator + ## of which control key was entered, allowing user to distinguish between + ## for example ctrl-C vs ctrl-D. + runnableExamples("-d:nimExperimentalLinenoiseExtra"): + if false: + var ret: ReadLineResult + while true: + readLineStatus("name: ", ret) # ctrl-D will exit, ctrl-C will go to next prompt + if ret.line.len > 0: echo ret.line + if ret.status == lnCtrlD: break + echo "exiting" + var data: linenoiseData + let buf = linenoiseExtra(prompt, data.addr) + result.line = $buf + free(buf) + result.status = data.status.ord.Status