From d8a7f6253b469ffc55432f137670f4599663d842 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Wed, 17 Aug 2022 12:50:36 +0300 Subject: [PATCH 01/16] Add Go 1.19 to testing targets --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afa0862..df5e264 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: - go: [ '1.17.x', '1.18.x' ] + go: [ '1.18.x', '1.19.x' ] steps: - name: Set up Go From 9b067cbb34ab309eca037c621987b1984fc00ff9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Mar 2023 04:00:41 +0000 Subject: [PATCH 02/16] Bump github.com/essentialkaos/check from 1.3.0 to 1.4.0 Bumps [github.com/essentialkaos/check](https://github.com/essentialkaos/check) from 1.3.0 to 1.4.0. - [Release notes](https://github.com/essentialkaos/check/releases) - [Commits](https://github.com/essentialkaos/check/compare/v1.3.0...v1.4.0) --- updated-dependencies: - dependency-name: github.com/essentialkaos/check dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 13ca70a..9d25542 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/essentialkaos/go-linenoise/v3 go 1.17 -require github.com/essentialkaos/check v1.3.0 +require github.com/essentialkaos/check v1.4.0 require ( github.com/kr/pretty v0.3.0 // indirect diff --git a/go.sum b/go.sum index 38819d3..1927cab 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/essentialkaos/check v1.3.0 h1:ria+8o22RCLdt2D/1SHQsEH5Mmy5S+iWHaGHrrbPUc0= -github.com/essentialkaos/check v1.3.0/go.mod h1:PhxzfJWlf5L/skuyhzBLIvjMB5Xu9TIyDIsqpY5MvB8= +github.com/essentialkaos/check v1.4.0 h1:kWdFxu9odCxUqo1NNFNJmguGrDHgwi3A8daXX1nkuKk= +github.com/essentialkaos/check v1.4.0/go.mod h1:LMKPZ2H+9PXe7Y2gEoKyVAwUqXVgx7KtgibfsHJPus0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= From 6997d6813f16a11045c7e19965c90b95db333f0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Mar 2023 05:17:14 +0000 Subject: [PATCH 03/16] Bump actions/setup-go from 3 to 4 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df5e264..2d9aa30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} id: go From 9e3481b907cbb57609af8a6c0c6286cc6a2822c0 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Fri, 14 Jul 2023 23:07:07 +0300 Subject: [PATCH 04/16] Improve CI workflow --- .github/workflows/ci.yml | 46 +++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d9aa30..e789ff6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,41 +6,49 @@ on: pull_request: branches: [master] +permissions: + actions: read + contents: read + statuses: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: Go: name: Go runs-on: ubuntu-latest - env: - SRC_DIR: src/github.com/${{ github.repository }} - GO111MODULE: auto - strategy: matrix: - go: [ '1.18.x', '1.19.x' ] + go: [ '1.18.x', '1.19.x', '1.20.x' ] steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Go uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} - id: go - - - name: Setup PATH - run: | - echo "GOPATH=${{ github.workspace }}" >> "$GITHUB_ENV" - echo "GOBIN=${{ github.workspace }}/bin" >> "$GITHUB_ENV" - echo "${{ github.workspace }}/bin" >> "$GITHUB_PATH" - - - name: Checkout - uses: actions/checkout@v3 - with: - path: ${{env.SRC_DIR}} - name: Download dependencies - working-directory: ${{env.SRC_DIR}} run: make deps - name: Run tests - working-directory: ${{env.SRC_DIR}} run: make test + + Typos: + name: Typos + runs-on: ubuntu-latest + + needs: Go + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Check spelling + continue-on-error: true + uses: crate-ci/typos@master From 0506321b33399ae0697bafa5baf9a149fd92cd91 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Fri, 14 Jul 2023 23:09:27 +0300 Subject: [PATCH 05/16] Add typos config --- .typos.toml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .typos.toml diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 0000000..7365108 --- /dev/null +++ b/.typos.toml @@ -0,0 +1,5 @@ +[files] +extend-exclude = ["go.sum", "*.c", "*.h"] + +[default.extend-identifiers] +O_WRONLY = "O_WRONLY" From f935e01259ba2d6cacf361d6aa49b3a12ec02147 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 03:40:00 +0000 Subject: [PATCH 06/16] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 4 ++-- .github/workflows/codeql.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e789ff6..bbc5443 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v4 @@ -47,7 +47,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check spelling continue-on-error: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bf5ecc9..0d8f170 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 2 From 41e83e60ae59a1589e35329bcc805eb3bbda4fdc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 04:25:39 +0000 Subject: [PATCH 07/16] Bump actions/setup-go from 4 to 5 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbc5443..e3525cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} From b4c66b1a198c47943d06dc6a4c6501366d74c7c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 04:06:04 +0000 Subject: [PATCH 08/16] Bump github/codeql-action from 2 to 3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0d8f170..bfc4df5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -25,9 +25,9 @@ jobs: fetch-depth: 2 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: go - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From 423b271fff2072e41f72bd11bda9c33c02113720 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 27 Apr 2024 11:45:33 +0300 Subject: [PATCH 09/16] Regenerate Makefile with the latest version of gomakegen --- Makefile | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 1c3279a..f28ffc7 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ ################################################################################ -# This Makefile generated by GoMakeGen 2.1.0 using next command: +# This Makefile generated by GoMakeGen 2.3.2 using next command: # gomakegen --mod . # # More info: https://kaos.sh/gomakegen @@ -13,6 +13,7 @@ ifdef VERBOSE ## Print verbose information (Flag) VERBOSE_FLAG = -v endif +COMPAT ?= 1.18 MAKEDIR = $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) GITREV ?= $(shell test -s $(MAKEDIR)/.git && git rev-parse --short HEAD) @@ -46,7 +47,7 @@ else endif ifdef COMPAT ## Compatible Go version (String) - go mod tidy $(VERBOSE_FLAG) -compat=$(COMPAT) + go mod tidy $(VERBOSE_FLAG) -compat=$(COMPAT) -go=$(COMPAT) else go mod tidy $(VERBOSE_FLAG) endif @@ -64,13 +65,13 @@ else go mod tidy $(VERBOSE_FLAG) endif - test -d vendor && go mod vendor $(VERBOSE_FLAG) || : + test -d vendor && rm -rf vendor && go mod vendor $(VERBOSE_FLAG) || : mod-download: go mod download mod-vendor: - go mod vendor $(VERBOSE_FLAG) + rm -rf vendor && go mod vendor $(VERBOSE_FLAG) fmt: ## Format source code with gofmt find . -name "*.go" -exec gofmt -s -w {} \; @@ -81,12 +82,12 @@ vet: ## Runs 'go vet' over sources help: ## Show this info @echo -e '\n\033[1mTargets:\033[0m\n' @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \ - | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[33m%-14s\033[0m %s\n", $$1, $$2}' + | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[33m%-6s\033[0m %s\n", $$1, $$2}' @echo -e '\n\033[1mVariables:\033[0m\n' @grep -E '^ifdef [A-Z_]+ .*?## .*$$' $(abspath $(lastword $(MAKEFILE_LIST))) \ | sed 's/ifdef //' \ - | awk 'BEGIN {FS = " .*?## "}; {printf " \033[32m%-14s\033[0m %s\n", $$1, $$2}' + | awk 'BEGIN {FS = " .*?## "}; {printf " \033[32m%-13s\033[0m %s\n", $$1, $$2}' @echo -e '' - @echo -e '\033[90mGenerated by GoMakeGen 2.1.0\033[0m\n' + @echo -e '\033[90mGenerated by GoMakeGen 2.3.2\033[0m\n' ################################################################################ From fd1fc76e4320d89b1ebe83e47edc36cdd4b8dd81 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 27 Apr 2024 11:48:28 +0300 Subject: [PATCH 10/16] Update linenoise to the latest version --- linenoise.c | 781 ++++++++++++++++++++++++++++++---------------------- linenoise.h | 44 ++- 2 files changed, 493 insertions(+), 332 deletions(-) diff --git a/linenoise.c b/linenoise.c index bb7686e..be95a7b 100644 --- a/linenoise.c +++ b/linenoise.c @@ -10,7 +10,7 @@ * * ------------------------------------------------------------------------ * - * Copyright (c) 2010-2016, Salvatore Sanfilippo + * Copyright (c) 2010-2023, Salvatore Sanfilippo * Copyright (c) 2010-2013, Pieter Noordhuis * * All rights reserved. @@ -124,6 +124,9 @@ static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; static linenoiseCompletionCallback *completionCallback = NULL; static linenoiseHintsCallback *hintsCallback = NULL; static linenoiseFreeHintsCallback *freeHintsCallback = NULL; +static char *linenoiseNoTTY(void); +static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags); +static void refreshLineWithFlags(struct linenoiseState *l, int flags); static struct termios orig_termios; /* In order to restore at exit.*/ static int maskmode = 0; /* Show "***" instead of input. For passwords. */ @@ -134,24 +137,6 @@ static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; static int history_len = 0; static char **history = NULL; -/* The linenoiseState structure represents the state during line editing. - * We pass this state to functions implementing specific editing - * functionalities. */ -struct linenoiseState { - int ifd; /* Terminal stdin file descriptor. */ - int ofd; /* Terminal stdout file descriptor. */ - char *buf; /* Edited line buffer. */ - size_t buflen; /* Edited line buffer size. */ - const char *prompt; /* Prompt to display. */ - size_t plen; /* Prompt length. */ - size_t pos; /* Current cursor position. */ - size_t oldcolpos; /* Previous refresh cursor column position. */ - size_t len; /* Current edited line length. */ - size_t cols; /* Number of columns in terminal. */ - size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ - int history_index; /* The history index we are currently editing. */ -}; - enum KEY_ACTION{ KEY_NULL = 0, /* NULL */ CTRL_A = 1, /* Ctrl+a */ @@ -176,6 +161,9 @@ enum KEY_ACTION{ static void linenoiseAtExit(void); int linenoiseHistoryAdd(const char *line); +#define REFRESH_CLEAN (1<<0) // Clean the old prompt from the screen +#define REFRESH_WRITE (1<<1) // Rewrite the prompt on the screen. +#define REFRESH_ALL (REFRESH_CLEAN|REFRESH_WRITE) // Do both. static void refreshLine(struct linenoiseState *l); /* Debugging macro. */ @@ -188,7 +176,7 @@ FILE *lndebug_fp = NULL; fprintf(lndebug_fp, \ "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ (int)l->len,(int)l->pos,(int)l->oldcolpos,plen,rows,rpos, \ - (int)l->maxrows,old_rows); \ + (int)l->oldrows,old_rows); \ } \ fprintf(lndebug_fp, ", " __VA_ARGS__); \ fflush(lndebug_fp); \ @@ -436,69 +424,99 @@ static void freeCompletions(linenoiseCompletions *lc) { free(lc->cvec); } -/* This is an helper function for linenoiseEdit() and is called when the +/* Called by completeLine() and linenoiseShow() to render the current + * edited line with the proposed completion. If the current completion table + * is already available, it is passed as second argument, otherwise the + * function will use the callback to obtain it. + * + * Flags are the same as refreshLine*(), that is REFRESH_* macros. */ +static void refreshLineWithCompletion(struct linenoiseState *ls, linenoiseCompletions *lc, int flags) { + /* Obtain the table of completions if the caller didn't provide one. */ + linenoiseCompletions ctable = { 0, NULL }; + if (lc == NULL) { + completionCallback(ls->buf,&ctable); + lc = &ctable; + } + + /* Show the edited line with completion if possible, or just refresh. */ + if (ls->completion_idx < lc->len) { + struct linenoiseState saved = *ls; + ls->len = ls->pos = strlen(lc->cvec[ls->completion_idx]); + ls->buf = lc->cvec[ls->completion_idx]; + refreshLineWithFlags(ls,flags); + ls->len = saved.len; + ls->pos = saved.pos; + ls->buf = saved.buf; + } else { + refreshLineWithFlags(ls,flags); + } + + /* Free the completions table if needed. */ + if (lc != &ctable) freeCompletions(&ctable); +} + +/* This is an helper function for linenoiseEdit*() and is called when the * user types the key in order to complete the string currently in the * input. * * The state of the editing is encapsulated into the pointed linenoiseState - * structure as described in the structure definition. */ -static int completeLine(struct linenoiseState *ls, char *cbuf, size_t cbuf_len, int *c) { + * structure as described in the structure definition. + * + * If the function returns non-zero, the caller should handle the + * returned value as a byte read from the standard input, and process + * it as usually: this basically means that the function may return a byte + * read from the termianl but not processed. Otherwise, if zero is returned, + * the input was consumed by the completeLine() function to navigate the + * possible completions, and the caller should read for the next characters + * from stdin. */ +static int completeLine(struct linenoiseState *ls, int keypressed) { linenoiseCompletions lc = { 0, NULL }; - int nread = 0, nwritten; - *c = 0; + int nwritten; + char c = keypressed; completionCallback(ls->buf,&lc); if (lc.len == 0) { linenoiseBeep(); + ls->in_completion = 0; } else { - size_t stop = 0, i = 0; - - while(!stop) { - /* Show completion or original buffer */ - if (i < lc.len) { - struct linenoiseState saved = *ls; - - ls->len = ls->pos = strlen(lc.cvec[i]); - ls->buf = lc.cvec[i]; - refreshLine(ls); - ls->len = saved.len; - ls->pos = saved.pos; - ls->buf = saved.buf; - } else { - refreshLine(ls); - } - - nread = readCode(ls->ifd,cbuf,cbuf_len,c); - if (nread <= 0) { - freeCompletions(&lc); - *c = -1; - return nread; - } + switch(c) { + case 9: /* tab */ + if (ls->in_completion == 0) { + ls->in_completion = 1; + ls->completion_idx = 0; + } else { + ls->completion_idx = (ls->completion_idx+1) % (lc.len+1); + if (ls->completion_idx == lc.len) linenoiseBeep(); + } + c = 0; + break; + case 27: /* escape */ + /* Re-show original buffer */ + if (ls->completion_idx < lc.len) refreshLine(ls); + ls->in_completion = 0; + c = 0; + break; + default: + /* Update buffer and return */ + if (ls->completion_idx < lc.len) { + nwritten = snprintf(ls->buf,ls->buflen,"%s", + lc.cvec[ls->completion_idx]); + ls->len = ls->pos = nwritten; + } + ls->in_completion = 0; + break; + } - switch(*c) { - case 9: /* tab */ - i = (i+1) % (lc.len+1); - if (i == lc.len) linenoiseBeep(); - break; - case 27: /* escape */ - /* Re-show original buffer */ - if (i < lc.len) refreshLine(ls); - stop = 1; - break; - default: - /* Update buffer and return */ - if (i < lc.len) { - nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); - ls->len = ls->pos = nwritten; - } - stop = 1; - break; - } + /* Show completion or original buffer */ + if (ls->in_completion && ls->completion_idx < lc.len) { + refreshLineWithCompletion(ls,&lc,REFRESH_ALL); + } else { + refreshLine(ls); } } freeCompletions(&lc); - return nread; + return c; /* Return last read character */ } /* Register a callback function to be called for tab-completion. */ @@ -632,8 +650,11 @@ static size_t promptTextColumnLen(const char *prompt, size_t plen) { /* Single line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, - * cursor position, and number of columns of the terminal. */ -static void refreshSingleLine(struct linenoiseState *l) { + * cursor position, and number of columns of the terminal. + * + * Flags is REFRESH_* macros. The function can just remove the old + * prompt, just write it, or both. */ +static void refreshSingleLine(struct linenoiseState *l, int flags) { char seq[64]; size_t pcollen = promptTextColumnLen(l->prompt,strlen(l->prompt)); int fd = l->ofd; @@ -654,23 +675,31 @@ static void refreshSingleLine(struct linenoiseState *l) { abInit(&ab); /* Cursor to left edge */ - snprintf(seq,64,"\r"); + snprintf(seq,sizeof(seq),"\r"); abAppend(&ab,seq,strlen(seq)); - /* Write the prompt and the current buffer content */ - abAppend(&ab,l->prompt,strlen(l->prompt)); - if (maskmode == 1) { - while (len--) abAppend(&ab,"*",1); - } else { - abAppend(&ab,buf,len); + + if (flags & REFRESH_WRITE) { + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (maskmode == 1) { + while (len--) abAppend(&ab,"*",1); + } else { + abAppend(&ab,buf,len); + } + /* Show hits if any. */ + refreshShowHints(&ab,l,pcollen); } - /* Show hits if any. */ - refreshShowHints(&ab,l,pcollen); + /* Erase to right */ - snprintf(seq,64,"\x1b[0K"); - abAppend(&ab,seq,strlen(seq)); - /* Move cursor to original position. */ - snprintf(seq,64,"\r\x1b[%dC", (int)(columnPos(buf,len,pos)+pcollen)); + snprintf(seq,sizeof(seq),"\x1b[0K"); abAppend(&ab,seq,strlen(seq)); + + if (flags & REFRESH_WRITE) { + /* Move cursor to original position. */ + snprintf(seq,sizeof(seq),"\r\x1b[%dC", (int)(columnPos(buf,len,pos)+pcollen)); + abAppend(&ab,seq,strlen(seq)); + } + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ abFree(&ab); } @@ -678,8 +707,11 @@ static void refreshSingleLine(struct linenoiseState *l) { /* Multi line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, - * cursor position, and number of columns of the terminal. */ -static void refreshMultiLine(struct linenoiseState *l) { + * cursor position, and number of columns of the terminal. + * + * Flags is REFRESH_* macros. The function can just remove the old + * prompt, just write it, or both. */ +static void refreshMultiLine(struct linenoiseState *l, int flags) { char seq[64]; size_t pcollen = promptTextColumnLen(l->prompt,strlen(l->prompt)); int colpos = columnPosForMultiLine(l->buf, l->len, l->len, l->cols, pcollen); @@ -688,83 +720,89 @@ static void refreshMultiLine(struct linenoiseState *l) { int rpos = (pcollen+l->oldcolpos+l->cols)/l->cols; /* cursor relative row. */ int rpos2; /* rpos after refresh. */ int col; /* colum position, zero-based. */ - int old_rows = l->maxrows; + int old_rows = l->oldrows; int fd = l->ofd, j; struct abuf ab; - /* Update maxrows if needed. */ - if (rows > (int)l->maxrows) l->maxrows = rows; + l->oldrows = rows; /* First step: clear all the lines used before. To do so start by * going to the last row. */ abInit(&ab); - if (old_rows-rpos > 0) { - lndebug("go down %d", old_rows-rpos); - snprintf(seq,64,"\x1b[%dB", old_rows-rpos); - abAppend(&ab,seq,strlen(seq)); - } - /* Now for every row clear it, go up. */ - for (j = 0; j < old_rows-1; j++) { - lndebug("clear+up"); - snprintf(seq,64,"\r\x1b[0K\x1b[1A"); - abAppend(&ab,seq,strlen(seq)); - } - - /* Clean the top line. */ - lndebug("clear"); - snprintf(seq,64,"\r\x1b[0K"); - abAppend(&ab,seq,strlen(seq)); + if (flags & REFRESH_CLEAN) { + if (old_rows-rpos > 0) { + lndebug("go down %d", old_rows-rpos); + snprintf(seq,64,"\x1b[%dB", old_rows-rpos); + abAppend(&ab,seq,strlen(seq)); + } - /* Write the prompt and the current buffer content */ - abAppend(&ab,l->prompt,strlen(l->prompt)); - if (maskmode == 1) { - unsigned int i; - for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); - } else { - abAppend(&ab,l->buf,l->len); + /* Now for every row clear it, go up. */ + for (j = 0; j < old_rows-1; j++) { + lndebug("clear+up"); + snprintf(seq,64,"\r\x1b[0K\x1b[1A"); + abAppend(&ab,seq,strlen(seq)); + } } - /* Show hits if any. */ - refreshShowHints(&ab,l,pcollen); + if (flags & REFRESH_ALL) { + /* Clean the top line. */ + lndebug("clear"); + snprintf(seq,64,"\r\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + } /* Get column length to cursor position */ colpos2 = columnPosForMultiLine(l->buf,l->len,l->pos,l->cols,pcollen); - /* 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 && - l->pos == l->len && - (colpos2+pcollen) % l->cols == 0) - { - lndebug(""); - abAppend(&ab,"\n",1); - snprintf(seq,64,"\r"); - abAppend(&ab,seq,strlen(seq)); - rows++; - if (rows > (int)l->maxrows) l->maxrows = rows; - } + if (flags & REFRESH_WRITE) { + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (maskmode == 1) { + unsigned int i; + for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); + } else { + abAppend(&ab,l->buf,l->len); + } + + /* Show hits if any. */ + refreshShowHints(&ab,l,pcollen); + + /* 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 && + l->pos == l->len && + (colpos2+pcollen) % l->cols == 0) + { + lndebug(""); + abAppend(&ab,"\n",1); + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + rows++; + if (rows > (int)l->oldrows) l->oldrows = rows; + } + + /* Move cursor to right position. */ + rpos2 = (pcollen+colpos2+l->cols)/l->cols; /* Current cursor relative row */ + lndebug("rpos2 %d", rpos2); - /* Move cursor to right position. */ - rpos2 = (pcollen+colpos2+l->cols)/l->cols; /* current cursor relative row. */ - lndebug("rpos2 %d", rpos2); + /* Go up till we reach the expected positon. */ + if (rows-rpos2 > 0) { + lndebug("go-up %d", rows-rpos2); + snprintf(seq,64,"\x1b[%dA", rows-rpos2); + abAppend(&ab,seq,strlen(seq)); + } - /* Go up till we reach the expected positon. */ - if (rows-rpos2 > 0) { - lndebug("go-up %d", rows-rpos2); - snprintf(seq,64,"\x1b[%dA", rows-rpos2); + /* Set column. */ + col = (pcollen+colpos2) % l->cols; + lndebug("set col %d", 1+col); + if (col) + snprintf(seq,64,"\r\x1b[%dC", col); + else + snprintf(seq,64,"\r"); abAppend(&ab,seq,strlen(seq)); } - /* Set column. */ - col = (pcollen + colpos2) % l->cols; - lndebug("set col %d", 1+col); - if (col) - snprintf(seq,64,"\r\x1b[%dC", col); - else - snprintf(seq,64,"\r"); - abAppend(&ab,seq,strlen(seq)); - lndebug("\n"); l->oldcolpos = colpos2; @@ -774,11 +812,33 @@ static void refreshMultiLine(struct linenoiseState *l) { /* Calls the two low level functions refreshSingleLine() or * refreshMultiLine() according to the selected mode. */ +static void refreshLineWithFlags(struct linenoiseState *l, int flags) { + if (mlmode) + refreshMultiLine(l,flags); + else + refreshSingleLine(l,flags); +} + +/* Utility function to avoid specifying REFRESH_ALL all the times. */ static void refreshLine(struct linenoiseState *l) { + refreshLineWithFlags(l,REFRESH_ALL); +} + +/* Hide the current line, when using the multiplexing API. */ +void linenoiseHide(struct linenoiseState *l) { if (mlmode) - refreshMultiLine(l); + refreshMultiLine(l,REFRESH_CLEAN); else - refreshSingleLine(l); + refreshSingleLine(l,REFRESH_CLEAN); +} + +/* Show the current line, when using the multiplexing API. */ +void linenoiseShow(struct linenoiseState *l) { + if (l->in_completion) { + refreshLineWithCompletion(l,NULL,REFRESH_WRITE); + } else { + refreshLineWithFlags(l,REFRESH_WRITE); + } } /* Insert the character 'c' at cursor current position. @@ -913,197 +973,278 @@ void linenoiseEditDeletePrevWord(struct linenoiseState *l) { refreshLine(l); } -/* This function is the core of the line editing capability of linenoise. - * It expects 'fd' to be already in "raw mode" so that every key pressed - * will be returned ASAP to read(). +/* This function is part of the multiplexed API of Linenoise, that is used + * in order to implement the blocking variant of the API but can also be + * called by the user directly in an event driven program. It will: * - * The resulting string is put into 'buf' when the user type enter, or - * when ctrl+d is typed. + * 1. Initialize the linenoise state passed by the user. + * 2. Put the terminal in RAW mode. + * 3. Show the prompt. + * 4. Return control to the user, that will have to call linenoiseEditFeed() + * each time there is some data arriving in the standard input. * - * 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) -{ - struct linenoiseState l; - + * The user can also call linenoiseEditHide() and linenoiseEditShow() if it + * is required to show some input arriving asyncronously, without mixing + * it with the currently edited line. + * + * When linenoiseEditFeed() returns non-NULL, the user finished with the + * line editing session (pressed enter CTRL-D/C): in this case the caller + * needs to call linenoiseEditStop() to put back the terminal in normal + * mode. This will not destroy the buffer, as long as the linenoiseState + * is still valid in the context of the caller. + * + * The function returns 0 on success, or -1 if writing to standard output + * fails. If stdin_fd or stdout_fd are set to -1, the default is to use + * STDIN_FILENO and STDOUT_FILENO. + */ +int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) { /* Populate the linenoise state that we pass to functions implementing * specific editing functionalities. */ - l.ifd = stdin_fd; - l.ofd = stdout_fd; - l.buf = buf; - l.buflen = buflen; - l.prompt = prompt; - l.plen = strlen(prompt); - l.oldcolpos = l.pos = 0; - l.len = 0; - l.cols = getColumns(stdin_fd, stdout_fd); - l.maxrows = 0; - l.history_index = 0; + l->in_completion = 0; + l->ifd = stdin_fd != -1 ? stdin_fd : STDIN_FILENO; + l->ofd = stdout_fd != -1 ? stdout_fd : STDOUT_FILENO; + l->buf = buf; + l->buflen = buflen; + l->prompt = prompt; + l->plen = strlen(prompt); + l->oldcolpos = l->pos = 0; + l->len = 0; + + /* Enter raw mode. */ + if (enableRawMode(l->ifd) == -1) return -1; + + l->cols = getColumns(stdin_fd, stdout_fd); + l->oldrows = 0; + l->history_index = 0; /* Buffer starts empty. */ - l.buf[0] = '\0'; - l.buflen--; /* Make sure there is always space for the nulterm */ + l->buf[0] = '\0'; + l->buflen--; /* Make sure there is always space for the nulterm */ + + /* If stdin is not a tty, stop here with the initialization. We + * will actually just read a line from standard input in blocking + * mode later, in linenoiseEditFeed(). */ + if (!isatty(l->ifd)) return 0; /* The latest history entry is always our current buffer, that * initially is just an empty string. */ linenoiseHistoryAdd(""); - if (write(l.ofd,prompt,l.plen) == -1) return -1; - while(1) { - int c; - char cbuf[32]; // large enough for any encoding? - int nread; - char seq[3]; - - nread = readCode(l.ifd,cbuf,sizeof(cbuf),&c); - if (nread <= 0) return l.len; - - /* 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. */ - if (c == 9 && completionCallback != NULL) { - nread = completeLine(&l,cbuf,sizeof(cbuf),&c); - /* Return on errors */ - if (c < 0) return l.len; - /* Read next character when 0 */ - if (c == 0) continue; - } + if (write(l->ofd,prompt,l->plen) == -1) return -1; + return 0; +} - switch(c) { - case ENTER: /* enter */ +char *linenoiseEditMore = "If you see this, you are misusing the API: when linenoiseEditFeed() is called, if it returns linenoiseEditMore the user is yet editing the line. See the README file for more information."; + +/* This function is part of the multiplexed API of linenoise, see the top + * comment on linenoiseEditStart() for more information. Call this function + * each time there is some data to read from the standard input file + * descriptor. In the case of blocking operations, this function can just be + * called in a loop, and block. + * + * The function returns linenoiseEditMore to signal that line editing is still + * in progress, that is, the user didn't yet pressed enter / CTRL-D. Otherwise + * the function returns the pointer to the heap-allocated buffer with the + * edited line, that the user should free with linenoiseFree(). + * + * On special conditions, NULL is returned and errno is populated: + * + * EAGAIN if the user pressed Ctrl-C + * ENOENT if the user pressed Ctrl-D + * + * Some other errno: I/O error. + */ +char *linenoiseEditFeed(struct linenoiseState *l) { + /* Not a TTY, pass control to line reading without character + * count limits. */ + if (!isatty(l->ifd)) return linenoiseNoTTY(); + + int c; + int nread; + char cbuf[32]; // large enough for any encoding? + char seq[3]; + + nread = readCode(l->ifd,cbuf,sizeof(cbuf),&c); + if (nread <= 0) return NULL; + + /* 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. */ + if ((l->in_completion || c == 9) && completionCallback != NULL) { + c = completeLine(l,c); + /* Return on errors */ + if (c < 0) return NULL; + /* Read next character when 0 */ + if (c == 0) return linenoiseEditMore; + } + + switch(c) { + case ENTER: /* enter */ + 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 strdup(l->buf); + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + return NULL; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the + line is empty, act as end-of-file. */ + if (l->len > 0) { + linenoiseEditDelete(l); + } else { 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; - return -1; - case BACKSPACE: /* backspace */ - case 8: /* ctrl-h */ - linenoiseEditBackspace(&l); - break; - case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the - line is empty, act as end-of-file. */ - if (l.len > 0) { - linenoiseEditDelete(&l); - } else { - history_len--; - free(history[history_len]); - return -1; - } - break; - case CTRL_T: /* ctrl-t, swaps current character with previous. */ - if (l.pos > 0 && l.pos < l.len) { - int aux = buf[l.pos-1]; - buf[l.pos-1] = buf[l.pos]; - buf[l.pos] = aux; - if (l.pos != l.len-1) l.pos++; - refreshLine(&l); - } - break; - case CTRL_B: /* ctrl-b */ - linenoiseEditMoveLeft(&l); - break; - case CTRL_F: /* ctrl-f */ - linenoiseEditMoveRight(&l); - break; - case CTRL_P: /* ctrl-p */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case CTRL_N: /* ctrl-n */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case ESC: /* escape sequence */ - /* Read the next two bytes representing the escape sequence. - * Use two calls to handle slow terminals returning the two - * chars at different times. */ - if (read(l.ifd,seq,1) == -1) break; - if (read(l.ifd,seq+1,1) == -1) break; - - /* ESC [ sequences. */ - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - /* Extended escape, read additional byte. */ - if (read(l.ifd,seq+2,1) == -1) break; - if (seq[2] == '~') { - switch(seq[1]) { - case '3': /* Delete key. */ - linenoiseEditDelete(&l); - break; - } - } - } else { + errno = ENOENT; + return NULL; + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l->pos > 0 && l->pos < l->len) { + int aux = l->buf[l->pos-1]; + l->buf[l->pos-1] = l->buf[l->pos]; + l->buf[l->pos] = aux; + if (l->pos != l->len-1) l->pos++; + refreshLine(l); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(l); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + if (read(l->ifd,seq,1) == -1) break; + if (read(l->ifd,seq+1,1) == -1) break; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(l->ifd,seq+2,1) == -1) break; + if (seq[2] == '~') { switch(seq[1]) { - case 'A': /* Up */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case 'B': /* Down */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case 'C': /* Right */ - linenoiseEditMoveRight(&l); - break; - case 'D': /* Left */ - linenoiseEditMoveLeft(&l); - break; - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); + case '3': /* Delete key. */ + linenoiseEditDelete(l); break; } } - } - - /* ESC O sequences. */ - else if (seq[0] == 'O') { + } else { switch(seq[1]) { + case 'A': /* Up */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_PREV); + break; + case 'B': /* Down */ + linenoiseEditHistoryNext(l, LINENOISE_HISTORY_NEXT); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(l); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(l); + break; case 'H': /* Home */ - linenoiseEditMoveHome(&l); + linenoiseEditMoveHome(l); break; case 'F': /* End*/ - linenoiseEditMoveEnd(&l); + linenoiseEditMoveEnd(l); break; } } - break; - default: - if (linenoiseEditInsert(&l,cbuf,nread)) return -1; - break; - case CTRL_U: /* Ctrl+u, delete the whole line. */ - buf[0] = '\0'; - l.pos = l.len = 0; - refreshLine(&l); - break; - case CTRL_K: /* Ctrl+k, delete from current to end of line. */ - buf[l.pos] = '\0'; - l.len = l.pos; - refreshLine(&l); - break; - case CTRL_A: /* Ctrl+a, go to the start of the line */ - linenoiseEditMoveHome(&l); - break; - case CTRL_E: /* ctrl+e, go to the end of the line */ - linenoiseEditMoveEnd(&l); - break; - case CTRL_L: /* ctrl+l, clear screen */ - linenoiseClearScreen(); - refreshLine(&l); - break; - case CTRL_W: /* ctrl+w, delete previous word */ - linenoiseEditDeletePrevWord(&l); - break; } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(l); + break; + } + } + break; + default: + if (linenoiseEditInsert(l,cbuf,nread)) return NULL; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + l->buf[0] = '\0'; + l->pos = l->len = 0; + refreshLine(l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + l->buf[l->pos] = '\0'; + l->len = l->pos; + refreshLine(l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(l); + break; + } + return linenoiseEditMore; +} + +/* This is part of the multiplexed linenoise API. See linenoiseEditStart() + * for more information. This function is called when linenoiseEditFeed() + * returns something different than NULL. At this point the user input + * is in the buffer, and we can restore the terminal in normal mode. */ +void linenoiseEditStop(struct linenoiseState *l) { + if (!isatty(l->ifd)) return; + disableRawMode(l->ifd); + printf("\n"); +} + +/* This just implements a blocking loop for the multiplexed API. + * In many applications that are not event-drivern, we can just call + * the blocking linenoise API, wait for the user to complete the editing + * and return the buffer. */ +static char *linenoiseBlockingEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) +{ + struct linenoiseState l; + + /* Editing without a buffer is invalid. */ + if (buflen == 0) { + errno = EINVAL; + return NULL; } - return l.len; + + linenoiseEditStart(&l,stdin_fd,stdout_fd,buf,buflen,prompt); + char *res; + while((res = linenoiseEditFeed(&l)) == linenoiseEditMore); + linenoiseEditStop(&l); + return res; } /* This special mode is used by linenoise in order to print scan codes @@ -1134,23 +1275,6 @@ void linenoisePrintKeyCodes(void) { disableRawMode(STDIN_FILENO); } -/* 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) { - int count; - - if (buflen == 0) { - errno = EINVAL; - return -1; - } - - if (enableRawMode(STDIN_FILENO) == -1) return -1; - count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); - disableRawMode(STDIN_FILENO); - printf("\n"); - return count; -} - /* This function is called when linenoise() is called with the standard * input file descriptor not attached to a TTY. So for example when the * program using linenoise is called in pipe or with a file redirected @@ -1194,7 +1318,6 @@ static char *linenoiseNoTTY(void) { * something even in the most desperate of the conditions. */ char *linenoise(const char *prompt) { char buf[LINENOISE_MAX_LINE]; - int count; if (!isatty(STDIN_FILENO)) { /* Not a tty: read from file / pipe. In this mode we don't want any @@ -1213,9 +1336,8 @@ char *linenoise(const char *prompt) { } return strdup(buf); } else { - count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); - if (count == -1) return NULL; - return strdup(buf); + char *retval = linenoiseBlockingEdit(STDIN_FILENO,STDOUT_FILENO,buf,LINENOISE_MAX_LINE,prompt); + return retval; } } @@ -1224,6 +1346,7 @@ char *linenoise(const char *prompt) { * created with. Useful when the main program is using an alternative * allocator. */ void linenoiseFree(void *ptr) { + if (ptr == linenoiseEditMore) return; // Protect from API misuse. free(ptr); } diff --git a/linenoise.h b/linenoise.h index ceca1eb..e5b0cb5 100644 --- a/linenoise.h +++ b/linenoise.h @@ -7,7 +7,7 @@ * * ------------------------------------------------------------------------ * - * Copyright (c) 2010-2014, Salvatore Sanfilippo + * Copyright (c) 2010-2023, Salvatore Sanfilippo * Copyright (c) 2010-2013, Pieter Noordhuis * * All rights reserved. @@ -43,11 +43,48 @@ extern "C" { #endif +#include /* For size_t. */ + +extern char *linenoiseEditMore; + +/* The linenoiseState structure represents the state during line editing. + * We pass this state to functions implementing specific editing + * functionalities. */ +struct linenoiseState { + int in_completion; /* The user pressed TAB and we are now in completion + * mode, so input is handled by completeLine(). */ + size_t completion_idx; /* Index of next completion to propose. */ + int ifd; /* Terminal stdin file descriptor. */ + int ofd; /* Terminal stdout file descriptor. */ + char *buf; /* Edited line buffer. */ + size_t buflen; /* Edited line buffer size. */ + const char *prompt; /* Prompt to display. */ + size_t plen; /* Prompt length. */ + size_t pos; /* Current cursor position. */ + size_t oldcolpos; /* Previous refresh cursor column position. */ + size_t len; /* Current edited line length. */ + size_t cols; /* Number of columns in terminal. */ + size_t oldrows; /* Rows used by last refrehsed line (multiline mode) */ + int history_index; /* The history index we are currently editing. */ +}; + typedef struct linenoiseCompletions { size_t len; char **cvec; } linenoiseCompletions; +/* Non blocking API. */ +int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt); +char *linenoiseEditFeed(struct linenoiseState *l); +void linenoiseEditStop(struct linenoiseState *l); +void linenoiseHide(struct linenoiseState *l); +void linenoiseShow(struct linenoiseState *l); + +/* Blocking API. */ +char *linenoise(const char *prompt); +void linenoiseFree(void *ptr); + +/* Completion API. */ typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); typedef void(linenoiseFreeHintsCallback)(void *); @@ -56,12 +93,13 @@ void linenoiseSetHintsCallback(linenoiseHintsCallback *); void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); void linenoiseAddCompletion(linenoiseCompletions *, const char *); -char *linenoise(const char *prompt); -void linenoiseFree(void *ptr); +/* History API. */ int linenoiseHistoryAdd(const char *line); int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySave(const char *filename); int linenoiseHistoryLoad(const char *filename); + +/* Other utilities. */ void linenoiseClearScreen(void); void linenoiseSetMultiLine(int ml); void linenoisePrintKeyCodes(void); From 6fb1108524371ba87d7580f708168d470ea805c6 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 27 Apr 2024 11:49:03 +0300 Subject: [PATCH 11/16] Remove version info --- version.go | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 version.go diff --git a/version.go b/version.go deleted file mode 100644 index db643a1..0000000 --- a/version.go +++ /dev/null @@ -1,8 +0,0 @@ -package linenoise - -// ///////////////////////////////////////////////////////////////////////////////// // - -// VERSION is current version of go-linenoise package -const VERSION = "3.4.0" - -// ///////////////////////////////////////////////////////////////////////////////// // From ff098d37b37a312b06b092f89bbee96db6a01f98 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 27 Apr 2024 11:50:51 +0300 Subject: [PATCH 12/16] Update CI workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3525cb..af72492 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: - go: [ '1.18.x', '1.19.x', '1.20.x' ] + go: [ '1.19.x', '1.20.x', '1.21.x' , '1.22.x' ] steps: - name: Checkout From 4fa0bac8a13dd1679e08c6f2fa872bc3d9504d8e Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 27 Apr 2024 11:59:05 +0300 Subject: [PATCH 13/16] Improve README --- README.md | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d8b971f..a8b1cad 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@

PkgGoDev GitHub Actions CI Status - GoReportCard + Code Climate Maintainability Codebeat badge GitHub Actions CodeQL Status

-

InstallationExampleBuild StatusLicense

+

ExampleCI StatusLicense


@@ -17,20 +17,6 @@ This is fork of [go.linenoise](https://github.com/GeertJohan/go.linenoise) package used in [EK](https://github.com/essentialkaos) projects. -### Installation - -For install, do: - -``` -go get github.com/essentialkaos/go-linenoise/v3 -``` - -For update to latest stable release, do: - -``` -go get -u github.com/essentialkaos/go-linenoise/v3 -``` - ### Example ```go @@ -41,7 +27,7 @@ package main import ( "fmt" - "github.com/essentialkaos/go-linenoise/v3" + linenoise "github.com/essentialkaos/go-linenoise/v3" ) // ////////////////////////////////////////////////////////////////////////// // @@ -59,7 +45,7 @@ func main() { ``` -### Build Status +### CI Status | Branch | Status | |--------|--------| From 702c0914ff649130f8a5d5e4475eaecfa4119f54 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 27 Apr 2024 12:03:09 +0300 Subject: [PATCH 14/16] Update CI workflow --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af72492..cdc8c0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,11 +18,13 @@ concurrency: jobs: Go: name: Go - runs-on: ubuntu-latest strategy: matrix: go: [ '1.19.x', '1.20.x', '1.21.x' , '1.22.x' ] + os: [ 'ubuntu-latest', 'macos-latest' ] + + runs-on: ${{ matrix.os }} steps: - name: Checkout From fa58b8f7d97fed429fe5903b3ac11efb2cd43d95 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 27 Apr 2024 12:07:15 +0300 Subject: [PATCH 15/16] Update CI workflow --- .github/workflows/ci.yml | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdc8c0d..645efa1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,15 +16,38 @@ concurrency: cancel-in-progress: true jobs: - Go: - name: Go + GoLinux: + name: Go (Linux) + runs-on: ubuntu-latest strategy: matrix: go: [ '1.19.x', '1.20.x', '1.21.x' , '1.22.x' ] - os: [ 'ubuntu-latest', 'macos-latest' ] - runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + + - name: Download dependencies + run: make deps + + - name: Run tests + run: make test + + GoMac: + name: Go (macOS) + runs-on: macos-latest + + needs: GoLinux + + strategy: + matrix: + go: [ '1.19.x', '1.20.x', '1.21.x' , '1.22.x' ] steps: - name: Checkout @@ -45,8 +68,6 @@ jobs: name: Typos runs-on: ubuntu-latest - needs: Go - steps: - name: Checkout uses: actions/checkout@v4 From ea2cf6535caacef829b83699e3dce795c0cb96f4 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 27 Apr 2024 12:13:16 +0300 Subject: [PATCH 16/16] Update CodeQL workflow --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bfc4df5..e9cd8f1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,7 +27,7 @@ jobs: - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: - languages: go + languages: go, c-cpp - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3