diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index afa0862..645efa1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,41 +6,72 @@ 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
+ GoLinux:
+ name: Go (Linux)
runs-on: ubuntu-latest
- env:
- SRC_DIR: src/github.com/${{ github.repository }}
- GO111MODULE: auto
-
strategy:
matrix:
- go: [ '1.17.x', '1.18.x' ]
+ go: [ '1.19.x', '1.20.x', '1.21.x' , '1.22.x' ]
steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
- name: Set up Go
- uses: actions/setup-go@v3
+ uses: actions/setup-go@v5
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: 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
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
+
+ - name: Set up Go
+ uses: actions/setup-go@v5
with:
- path: ${{env.SRC_DIR}}
+ go-version: ${{ matrix.go }}
- 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
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Check spelling
+ continue-on-error: true
+ uses: crate-ci/typos@master
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index bf5ecc9..e9cd8f1 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -20,14 +20,14 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@v3
with:
- languages: go
+ languages: go, c-cpp
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@v3
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"
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'
################################################################################
diff --git a/README.md b/README.md
index d8b971f..a8b1cad 100644
--- a/README.md
+++ b/README.md
@@ -3,13 +3,13 @@
-
+
-Installation • Example • Build Status • License
+Example • CI Status • License
@@ -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 |
|--------|--------|
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=
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);
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"
-
-// ///////////////////////////////////////////////////////////////////////////////// //