linenoise.c 1.2025.9 → 2.2026.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{README.markdown → README.md} +73 -27
- package/{linenoise.c → linenoise/linenoise.c} +490 -77
- package/linenoise/linenoise.h +114 -0
- package/linenoise.h +4 -112
- package/package.json +23 -7
- package/Makefile +0 -7
|
@@ -1,38 +1,17 @@
|
|
|
1
1
|
# Linenoise
|
|
2
2
|
|
|
3
3
|
A minimal, zero-config, BSD licensed, readline replacement used in Redis,
|
|
4
|
-
MongoDB, Android and many other projects.
|
|
4
|
+
MongoDB, Android and many other projects.
|
|
5
5
|
|
|
6
6
|
* Single and multi line editing mode with the usual key bindings implemented.
|
|
7
7
|
* History handling.
|
|
8
8
|
* Completion.
|
|
9
9
|
* Hints (suggestions at the right of the prompt as you type).
|
|
10
10
|
* Multiplexing mode, with prompt hiding/restoring for asynchronous output.
|
|
11
|
-
*
|
|
11
|
+
* UTF-8 support for multi-byte characters and emoji.
|
|
12
|
+
* About ~1100 lines (comments and spaces excluded) of BSD license source code.
|
|
12
13
|
* Only uses a subset of VT100 escapes (ANSI.SYS compatible).
|
|
13
14
|
|
|
14
|
-
## Installation
|
|
15
|
-
|
|
16
|
-
Run:
|
|
17
|
-
```bash
|
|
18
|
-
$ npm i linenoise.c
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
And then include `linenoise.h` as follows:
|
|
22
|
-
```c
|
|
23
|
-
#include "node_modules/linenoise.c/linenoise.h"
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
You may also want to include `linenoise.c` as follows:
|
|
27
|
-
```c
|
|
28
|
-
#ifndef __LINENOISE_C__
|
|
29
|
-
#define __LINENOISE_C__
|
|
30
|
-
#include "node_modules/linenoise.c/linenoise.c"
|
|
31
|
-
#endif
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
This will include both the function declaration and their definitions into a single file.
|
|
35
|
-
|
|
36
15
|
## Can a line editing library be 20k lines of code?
|
|
37
16
|
|
|
38
17
|
Line editing with some support for history is a really important feature for command line utilities. Instead of retyping almost the same stuff again and again it's just much better to hit the up arrow and edit on syntax errors, or in order to try a slightly different command. But apparently code dealing with terminals is some sort of Black Magic: readline is 30k lines of code, libedit 20k. Is it reasonable to link small utilities to huge libraries just to get a minimal support for line editing?
|
|
@@ -41,7 +20,7 @@ So what usually happens is either:
|
|
|
41
20
|
|
|
42
21
|
* Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (real world example of this problem: Tclsh).
|
|
43
22
|
* Smaller programs not using a configure script not supporting line editing at all (A problem we had with `redis-cli`, for instance).
|
|
44
|
-
|
|
23
|
+
|
|
45
24
|
The result is a pollution of binaries without line editing support.
|
|
46
25
|
|
|
47
26
|
So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporting line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not.
|
|
@@ -78,6 +57,39 @@ easy to understand code.
|
|
|
78
57
|
|
|
79
58
|
Send feedbacks to antirez at gmail
|
|
80
59
|
|
|
60
|
+
|
|
61
|
+
# Installation
|
|
62
|
+
|
|
63
|
+
Run:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
$ npm i linenoise.c
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
And then include `linenoise.h` as follows:
|
|
70
|
+
|
|
71
|
+
```c
|
|
72
|
+
// main.c
|
|
73
|
+
#include <linenoise.h>
|
|
74
|
+
|
|
75
|
+
int main() { /* ... */ }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Finally, compile while adding the path `node_modules/linenoise.c` to your compiler's include paths.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
$ clang -I./node_modules/linenoise.c main.c # or, use gcc
|
|
82
|
+
$ gcc -I./node_modules/linenoise.c main.c
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
You may also use a simpler approach with the [cpoach](https://www.npmjs.com/package/cpoach.sh) tool, which automatically adds the necessary include paths of all the installed dependencies for your project.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
$ cpoach clang main.c # or, use gcc
|
|
89
|
+
$ cpoach gcc main.c
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
|
|
81
93
|
# The API
|
|
82
94
|
|
|
83
95
|
Linenoise is very easy to use, and reading the example shipped with the
|
|
@@ -155,7 +167,7 @@ just that. Both functions return -1 on error and 0 on success.
|
|
|
155
167
|
|
|
156
168
|
Sometimes it is useful to allow the user to type passwords or other
|
|
157
169
|
secrets that should not be displayed. For such situations linenoise supports
|
|
158
|
-
a "mask mode" that will just replace the characters the user is typing
|
|
170
|
+
a "mask mode" that will just replace the characters the user is typing
|
|
159
171
|
with `*` characters, like in the following example:
|
|
160
172
|
|
|
161
173
|
$ ./linenoise_example
|
|
@@ -363,14 +375,48 @@ example using select(2) and the asynchronous API:
|
|
|
363
375
|
|
|
364
376
|
You can test the example by running the example program with the `--async` option.
|
|
365
377
|
|
|
378
|
+
## Running the tests
|
|
379
|
+
|
|
380
|
+
To run the test suite:
|
|
381
|
+
|
|
382
|
+
make test
|
|
383
|
+
|
|
384
|
+
The tests will display a virtual terminal showing linenoise output in real-time, making it easy to see what's being tested and debug any failures.
|
|
385
|
+
|
|
386
|
+
### What the tests cover
|
|
387
|
+
|
|
388
|
+
The test suite verifies:
|
|
389
|
+
|
|
390
|
+
* Basic typing and cursor movement (left, right, home, end)
|
|
391
|
+
* Backspace and delete operations
|
|
392
|
+
* UTF-8 multi-byte characters (accented letters, CJK)
|
|
393
|
+
* Emoji and grapheme clusters (skin tones, ZWJ sequences like flags)
|
|
394
|
+
* Horizontal scrolling for long lines
|
|
395
|
+
* Multiline mode editing and navigation
|
|
396
|
+
* History navigation in multiline mode
|
|
397
|
+
* Word and line deletion (Ctrl-W, Ctrl-U)
|
|
398
|
+
|
|
399
|
+
### How the test harness works
|
|
400
|
+
|
|
401
|
+
The test program (`linenoise-test.c`) implements a VT100 terminal emulator that captures and verifies linenoise output:
|
|
402
|
+
|
|
403
|
+
1. **Fork and pipes**: The test harness forks `linenoise-example`, connecting to it via pipes. The child process sees `LINENOISE_ASSUME_TTY=1` to enable terminal mode despite not having a real TTY.
|
|
404
|
+
2. **VT100 emulator**: A minimal VT100 emulator parses escape sequences (cursor movement, screen clearing, etc.) and maintains a virtual screen buffer. Each cell stores a complete UTF-8 grapheme cluster and its display width.
|
|
405
|
+
3. **Visual rendering**: After each operation, the virtual screen is rendered to your real terminal with a border, so you can watch the test execute and see exactly what linenoise is displaying.
|
|
406
|
+
4. **Assertions**: Tests verify screen contents and cursor position against expected values.
|
|
407
|
+
|
|
408
|
+
This approach tests linenoise as users actually experience it, catching rendering bugs that unit tests would miss.
|
|
409
|
+
|
|
366
410
|
## Related projects
|
|
367
411
|
|
|
368
|
-
* [Linenoise NG](https://github.com/arangodb/linenoise-ng) is a fork of Linenoise that aims to add more advanced features like
|
|
412
|
+
* [Linenoise NG](https://github.com/arangodb/linenoise-ng) is a fork of Linenoise that aims to add more advanced features like Windows support and other features. Uses C++ instead of C as development language.
|
|
369
413
|
* [Linenoise-swift](https://github.com/andybest/linenoise-swift) is a reimplementation of Linenoise written in Swift.
|
|
370
414
|
|
|
371
415
|
<br>
|
|
372
416
|
<br>
|
|
373
417
|
|
|
374
418
|
|
|
419
|
+
[](https://wolfram77.github.io)<br>
|
|
420
|
+
[](https://github.com/antirez/linenoise)
|
|
375
421
|
[](https://nodef.github.io)
|
|
376
422
|

|
|
@@ -115,6 +115,7 @@
|
|
|
115
115
|
#include <sys/types.h>
|
|
116
116
|
#include <sys/ioctl.h>
|
|
117
117
|
#include <unistd.h>
|
|
118
|
+
#include <stdint.h>
|
|
118
119
|
#include "linenoise.h"
|
|
119
120
|
|
|
120
121
|
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
|
|
@@ -136,6 +137,315 @@ static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
|
|
|
136
137
|
static int history_len = 0;
|
|
137
138
|
static char **history = NULL;
|
|
138
139
|
|
|
140
|
+
/* =========================== UTF-8 support ================================ */
|
|
141
|
+
|
|
142
|
+
/* Return the number of bytes that compose the UTF-8 character starting at
|
|
143
|
+
* 'c'. This function assumes a valid UTF-8 encoding and handles the four
|
|
144
|
+
* standard byte patterns:
|
|
145
|
+
* 0xxxxxxx -> 1 byte (ASCII)
|
|
146
|
+
* 110xxxxx -> 2 bytes
|
|
147
|
+
* 1110xxxx -> 3 bytes
|
|
148
|
+
* 11110xxx -> 4 bytes */
|
|
149
|
+
static int utf8ByteLen(char c) {
|
|
150
|
+
unsigned char uc = (unsigned char)c;
|
|
151
|
+
if ((uc & 0x80) == 0) return 1; /* 0xxxxxxx: ASCII */
|
|
152
|
+
if ((uc & 0xE0) == 0xC0) return 2; /* 110xxxxx: 2-byte seq */
|
|
153
|
+
if ((uc & 0xF0) == 0xE0) return 3; /* 1110xxxx: 3-byte seq */
|
|
154
|
+
if ((uc & 0xF8) == 0xF0) return 4; /* 11110xxx: 4-byte seq */
|
|
155
|
+
return 1; /* Fallback for invalid encoding, treat as single byte. */
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* Decode a UTF-8 sequence starting at 's' into a Unicode codepoint.
|
|
159
|
+
* Returns the codepoint value. Assumes valid UTF-8 encoding. */
|
|
160
|
+
static uint32_t utf8DecodeChar(const char *s, size_t *len) {
|
|
161
|
+
unsigned char *p = (unsigned char *)s;
|
|
162
|
+
uint32_t cp;
|
|
163
|
+
|
|
164
|
+
if ((*p & 0x80) == 0) {
|
|
165
|
+
*len = 1;
|
|
166
|
+
return *p;
|
|
167
|
+
} else if ((*p & 0xE0) == 0xC0) {
|
|
168
|
+
*len = 2;
|
|
169
|
+
cp = (*p & 0x1F) << 6;
|
|
170
|
+
cp |= (p[1] & 0x3F);
|
|
171
|
+
return cp;
|
|
172
|
+
} else if ((*p & 0xF0) == 0xE0) {
|
|
173
|
+
*len = 3;
|
|
174
|
+
cp = (*p & 0x0F) << 12;
|
|
175
|
+
cp |= (p[1] & 0x3F) << 6;
|
|
176
|
+
cp |= (p[2] & 0x3F);
|
|
177
|
+
return cp;
|
|
178
|
+
} else if ((*p & 0xF8) == 0xF0) {
|
|
179
|
+
*len = 4;
|
|
180
|
+
cp = (*p & 0x07) << 18;
|
|
181
|
+
cp |= (p[1] & 0x3F) << 12;
|
|
182
|
+
cp |= (p[2] & 0x3F) << 6;
|
|
183
|
+
cp |= (p[3] & 0x3F);
|
|
184
|
+
return cp;
|
|
185
|
+
}
|
|
186
|
+
*len = 1;
|
|
187
|
+
return *p; /* Fallback for invalid sequences. */
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* Check if codepoint is a variation selector (emoji style modifiers). */
|
|
191
|
+
static int isVariationSelector(uint32_t cp) {
|
|
192
|
+
return cp == 0xFE0E || cp == 0xFE0F; /* Text/emoji style */
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Check if codepoint is a skin tone modifier. */
|
|
196
|
+
static int isSkinToneModifier(uint32_t cp) {
|
|
197
|
+
return cp >= 0x1F3FB && cp <= 0x1F3FF;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Check if codepoint is Zero Width Joiner. */
|
|
201
|
+
static int isZWJ(uint32_t cp) {
|
|
202
|
+
return cp == 0x200D;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* Check if codepoint is a Regional Indicator (for flag emoji). */
|
|
206
|
+
static int isRegionalIndicator(uint32_t cp) {
|
|
207
|
+
return cp >= 0x1F1E6 && cp <= 0x1F1FF;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Check if codepoint is a combining mark or other zero-width character. */
|
|
211
|
+
static int isCombiningMark(uint32_t cp) {
|
|
212
|
+
return (cp >= 0x0300 && cp <= 0x036F) || /* Combining Diacriticals */
|
|
213
|
+
(cp >= 0x1AB0 && cp <= 0x1AFF) || /* Combining Diacriticals Extended */
|
|
214
|
+
(cp >= 0x1DC0 && cp <= 0x1DFF) || /* Combining Diacriticals Supplement */
|
|
215
|
+
(cp >= 0x20D0 && cp <= 0x20FF) || /* Combining Diacriticals for Symbols */
|
|
216
|
+
(cp >= 0xFE20 && cp <= 0xFE2F); /* Combining Half Marks */
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Check if codepoint extends the previous character (doesn't start a new grapheme). */
|
|
220
|
+
static int isGraphemeExtend(uint32_t cp) {
|
|
221
|
+
return isVariationSelector(cp) || isSkinToneModifier(cp) ||
|
|
222
|
+
isZWJ(cp) || isCombiningMark(cp);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* Decode the UTF-8 codepoint ending at position 'pos' (exclusive) and
|
|
226
|
+
* return its value. Also sets *cplen to the byte length of the codepoint. */
|
|
227
|
+
static uint32_t utf8DecodePrev(const char *buf, size_t pos, size_t *cplen) {
|
|
228
|
+
if (pos == 0) {
|
|
229
|
+
*cplen = 0;
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
/* Scan backwards to find the start byte. */
|
|
233
|
+
size_t i = pos;
|
|
234
|
+
do {
|
|
235
|
+
i--;
|
|
236
|
+
} while (i > 0 && (pos - i) < 4 && ((unsigned char)buf[i] & 0xC0) == 0x80);
|
|
237
|
+
*cplen = pos - i;
|
|
238
|
+
size_t dummy;
|
|
239
|
+
return utf8DecodeChar(buf + i, &dummy);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* Given a buffer and a position, return the byte length of the grapheme
|
|
243
|
+
* cluster before that position. A grapheme cluster includes:
|
|
244
|
+
* - The base character
|
|
245
|
+
* - Any following variation selectors, skin tone modifiers
|
|
246
|
+
* - ZWJ sequences (emoji joined by Zero Width Joiner)
|
|
247
|
+
* - Regional indicator pairs (flag emoji) */
|
|
248
|
+
static size_t utf8PrevCharLen(const char *buf, size_t pos) {
|
|
249
|
+
if (pos == 0) return 0;
|
|
250
|
+
|
|
251
|
+
size_t total = 0;
|
|
252
|
+
size_t curpos = pos;
|
|
253
|
+
|
|
254
|
+
/* First, get the last codepoint. */
|
|
255
|
+
size_t cplen;
|
|
256
|
+
uint32_t cp = utf8DecodePrev(buf, curpos, &cplen);
|
|
257
|
+
if (cplen == 0) return 0;
|
|
258
|
+
total += cplen;
|
|
259
|
+
curpos -= cplen;
|
|
260
|
+
|
|
261
|
+
/* If we're at an extending character, we need to find what it extends.
|
|
262
|
+
* Keep going back through the grapheme cluster. */
|
|
263
|
+
while (curpos > 0) {
|
|
264
|
+
size_t prevlen;
|
|
265
|
+
uint32_t prevcp = utf8DecodePrev(buf, curpos, &prevlen);
|
|
266
|
+
if (prevlen == 0) break;
|
|
267
|
+
|
|
268
|
+
if (isZWJ(prevcp)) {
|
|
269
|
+
/* ZWJ joins two emoji. Include the ZWJ and continue to get
|
|
270
|
+
* the preceding character. */
|
|
271
|
+
total += prevlen;
|
|
272
|
+
curpos -= prevlen;
|
|
273
|
+
/* Now get the character before ZWJ. */
|
|
274
|
+
prevcp = utf8DecodePrev(buf, curpos, &prevlen);
|
|
275
|
+
if (prevlen == 0) break;
|
|
276
|
+
total += prevlen;
|
|
277
|
+
curpos -= prevlen;
|
|
278
|
+
cp = prevcp;
|
|
279
|
+
continue; /* Check if there's more extending before this. */
|
|
280
|
+
} else if (isGraphemeExtend(cp)) {
|
|
281
|
+
/* Current cp is an extending character; include previous. */
|
|
282
|
+
total += prevlen;
|
|
283
|
+
curpos -= prevlen;
|
|
284
|
+
cp = prevcp;
|
|
285
|
+
continue;
|
|
286
|
+
} else if (isRegionalIndicator(cp) && isRegionalIndicator(prevcp)) {
|
|
287
|
+
/* Two regional indicators form a flag. But we need to be careful:
|
|
288
|
+
* flags are always pairs, so only join if we're at an even boundary.
|
|
289
|
+
* For simplicity, just join one pair. */
|
|
290
|
+
total += prevlen;
|
|
291
|
+
curpos -= prevlen;
|
|
292
|
+
break;
|
|
293
|
+
} else {
|
|
294
|
+
/* No more extending; we've found the start of the cluster. */
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return total;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/* Given a buffer, position and total length, return the byte length of the
|
|
303
|
+
* grapheme cluster at the current position. */
|
|
304
|
+
static size_t utf8NextCharLen(const char *buf, size_t pos, size_t len) {
|
|
305
|
+
if (pos >= len) return 0;
|
|
306
|
+
|
|
307
|
+
size_t total = 0;
|
|
308
|
+
size_t curpos = pos;
|
|
309
|
+
|
|
310
|
+
/* Get the first codepoint. */
|
|
311
|
+
size_t cplen;
|
|
312
|
+
uint32_t cp = utf8DecodeChar(buf + curpos, &cplen);
|
|
313
|
+
total += cplen;
|
|
314
|
+
curpos += cplen;
|
|
315
|
+
|
|
316
|
+
int isRI = isRegionalIndicator(cp);
|
|
317
|
+
|
|
318
|
+
/* Consume any extending characters that follow. */
|
|
319
|
+
while (curpos < len) {
|
|
320
|
+
size_t nextlen;
|
|
321
|
+
uint32_t nextcp = utf8DecodeChar(buf + curpos, &nextlen);
|
|
322
|
+
|
|
323
|
+
if (isZWJ(nextcp) && curpos + nextlen < len) {
|
|
324
|
+
/* ZWJ: include it and the following character. */
|
|
325
|
+
total += nextlen;
|
|
326
|
+
curpos += nextlen;
|
|
327
|
+
/* Get the character after ZWJ. */
|
|
328
|
+
nextcp = utf8DecodeChar(buf + curpos, &nextlen);
|
|
329
|
+
total += nextlen;
|
|
330
|
+
curpos += nextlen;
|
|
331
|
+
continue; /* Check for more extending after the joined char. */
|
|
332
|
+
} else if (isGraphemeExtend(nextcp)) {
|
|
333
|
+
/* Variation selector, skin tone, combining mark, etc. */
|
|
334
|
+
total += nextlen;
|
|
335
|
+
curpos += nextlen;
|
|
336
|
+
continue;
|
|
337
|
+
} else if (isRI && isRegionalIndicator(nextcp)) {
|
|
338
|
+
/* Second regional indicator for a flag pair. */
|
|
339
|
+
total += nextlen;
|
|
340
|
+
curpos += nextlen;
|
|
341
|
+
isRI = 0; /* Only pair once. */
|
|
342
|
+
continue;
|
|
343
|
+
} else {
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return total;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* Return the display width of a Unicode codepoint. This is a heuristic
|
|
352
|
+
* that works for most common cases:
|
|
353
|
+
* - Control chars and zero-width: 0 columns
|
|
354
|
+
* - Grapheme-extending chars (VS, skin tone, ZWJ): 0 columns
|
|
355
|
+
* - ASCII printable: 1 column
|
|
356
|
+
* - Wide chars (CJK, emoji, fullwidth): 2 columns
|
|
357
|
+
* - Everything else: 1 column
|
|
358
|
+
*
|
|
359
|
+
* This is not a full wcwidth() implementation, but a minimal heuristic
|
|
360
|
+
* that handles emoji and CJK characters reasonably well. */
|
|
361
|
+
static int utf8CharWidth(uint32_t cp) {
|
|
362
|
+
/* Control characters and combining marks: zero width. */
|
|
363
|
+
if (cp < 32 || (cp >= 0x7F && cp < 0xA0)) return 0;
|
|
364
|
+
if (isCombiningMark(cp)) return 0;
|
|
365
|
+
|
|
366
|
+
/* Grapheme-extending characters: zero width.
|
|
367
|
+
* These modify the preceding character rather than taking space. */
|
|
368
|
+
if (isVariationSelector(cp)) return 0;
|
|
369
|
+
if (isSkinToneModifier(cp)) return 0;
|
|
370
|
+
if (isZWJ(cp)) return 0;
|
|
371
|
+
|
|
372
|
+
/* Wide character ranges - these display as 2 columns:
|
|
373
|
+
* - CJK Unified Ideographs and Extensions
|
|
374
|
+
* - Fullwidth forms
|
|
375
|
+
* - Various emoji ranges */
|
|
376
|
+
if (cp >= 0x1100 &&
|
|
377
|
+
(cp <= 0x115F || /* Hangul Jamo */
|
|
378
|
+
cp == 0x2329 || cp == 0x232A || /* Angle brackets */
|
|
379
|
+
(cp >= 0x231A && cp <= 0x231B) || /* Watch, Hourglass */
|
|
380
|
+
(cp >= 0x23E9 && cp <= 0x23F3) || /* Various symbols */
|
|
381
|
+
(cp >= 0x23F8 && cp <= 0x23FA) || /* Various symbols */
|
|
382
|
+
(cp >= 0x25AA && cp <= 0x25AB) || /* Small squares */
|
|
383
|
+
(cp >= 0x25B6 && cp <= 0x25C0) || /* Play/reverse buttons */
|
|
384
|
+
(cp >= 0x25FB && cp <= 0x25FE) || /* Squares */
|
|
385
|
+
(cp >= 0x2600 && cp <= 0x26FF) || /* Misc Symbols (sun, cloud, etc) */
|
|
386
|
+
(cp >= 0x2700 && cp <= 0x27BF) || /* Dingbats (❤, ✂, etc) */
|
|
387
|
+
(cp >= 0x2934 && cp <= 0x2935) || /* Arrows */
|
|
388
|
+
(cp >= 0x2B05 && cp <= 0x2B07) || /* Arrows */
|
|
389
|
+
(cp >= 0x2B1B && cp <= 0x2B1C) || /* Squares */
|
|
390
|
+
cp == 0x2B50 || cp == 0x2B55 || /* Star, circle */
|
|
391
|
+
(cp >= 0x2E80 && cp <= 0xA4CF &&
|
|
392
|
+
cp != 0x303F) || /* CJK ... Yi */
|
|
393
|
+
(cp >= 0xAC00 && cp <= 0xD7A3) || /* Hangul Syllables */
|
|
394
|
+
(cp >= 0xF900 && cp <= 0xFAFF) || /* CJK Compatibility Ideographs */
|
|
395
|
+
(cp >= 0xFE10 && cp <= 0xFE1F) || /* Vertical forms */
|
|
396
|
+
(cp >= 0xFE30 && cp <= 0xFE6F) || /* CJK Compatibility Forms */
|
|
397
|
+
(cp >= 0xFF00 && cp <= 0xFF60) || /* Fullwidth Forms */
|
|
398
|
+
(cp >= 0xFFE0 && cp <= 0xFFE6) || /* Fullwidth Signs */
|
|
399
|
+
(cp >= 0x1F1E6 && cp <= 0x1F1FF) || /* Regional Indicators (flags) */
|
|
400
|
+
(cp >= 0x1F300 && cp <= 0x1F64F) || /* Misc Symbols and Emoticons */
|
|
401
|
+
(cp >= 0x1F680 && cp <= 0x1F6FF) || /* Transport and Map Symbols */
|
|
402
|
+
(cp >= 0x1F900 && cp <= 0x1F9FF) || /* Supplemental Symbols */
|
|
403
|
+
(cp >= 0x1FA00 && cp <= 0x1FAFF) || /* Chess, Extended-A */
|
|
404
|
+
(cp >= 0x20000 && cp <= 0x2FFFF))) /* CJK Extension B and beyond */
|
|
405
|
+
return 2;
|
|
406
|
+
|
|
407
|
+
return 1; /* Default: single width */
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/* Calculate the display width of a UTF-8 string of 'len' bytes.
|
|
411
|
+
* This is used for cursor positioning in the terminal.
|
|
412
|
+
* Handles grapheme clusters: characters joined by ZWJ contribute 0 width
|
|
413
|
+
* after the first character in the sequence. */
|
|
414
|
+
static size_t utf8StrWidth(const char *s, size_t len) {
|
|
415
|
+
size_t width = 0;
|
|
416
|
+
size_t i = 0;
|
|
417
|
+
int after_zwj = 0; /* Track if previous char was ZWJ */
|
|
418
|
+
|
|
419
|
+
while (i < len) {
|
|
420
|
+
size_t clen;
|
|
421
|
+
uint32_t cp = utf8DecodeChar(s + i, &clen);
|
|
422
|
+
|
|
423
|
+
if (after_zwj) {
|
|
424
|
+
/* Character after ZWJ: don't add width, it's joined.
|
|
425
|
+
* But do check for extending chars after it. */
|
|
426
|
+
after_zwj = 0;
|
|
427
|
+
} else {
|
|
428
|
+
width += utf8CharWidth(cp);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/* Check if this is a ZWJ - next char will be joined. */
|
|
432
|
+
if (isZWJ(cp)) {
|
|
433
|
+
after_zwj = 1;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
i += clen;
|
|
437
|
+
}
|
|
438
|
+
return width;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* Return the display width of a single UTF-8 character at position 's'. */
|
|
442
|
+
static int utf8SingleCharWidth(const char *s, size_t len) {
|
|
443
|
+
if (len == 0) return 0;
|
|
444
|
+
size_t clen;
|
|
445
|
+
uint32_t cp = utf8DecodeChar(s, &clen);
|
|
446
|
+
return utf8CharWidth(cp);
|
|
447
|
+
}
|
|
448
|
+
|
|
139
449
|
enum KEY_ACTION{
|
|
140
450
|
KEY_NULL = 0, /* NULL */
|
|
141
451
|
CTRL_A = 1, /* Ctrl+a */
|
|
@@ -220,6 +530,13 @@ static int isUnsupportedTerm(void) {
|
|
|
220
530
|
static int enableRawMode(int fd) {
|
|
221
531
|
struct termios raw;
|
|
222
532
|
|
|
533
|
+
/* Test mode: when LINENOISE_ASSUME_TTY is set, skip terminal setup.
|
|
534
|
+
* This allows testing via pipes without a real terminal. */
|
|
535
|
+
if (getenv("LINENOISE_ASSUME_TTY")) {
|
|
536
|
+
rawmode = 1;
|
|
537
|
+
return 0;
|
|
538
|
+
}
|
|
539
|
+
|
|
223
540
|
if (!isatty(STDIN_FILENO)) goto fatal;
|
|
224
541
|
if (!atexit_registered) {
|
|
225
542
|
atexit(linenoiseAtExit);
|
|
@@ -253,6 +570,11 @@ fatal:
|
|
|
253
570
|
}
|
|
254
571
|
|
|
255
572
|
static void disableRawMode(int fd) {
|
|
573
|
+
/* Test mode: nothing to restore. */
|
|
574
|
+
if (getenv("LINENOISE_ASSUME_TTY")) {
|
|
575
|
+
rawmode = 0;
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
256
578
|
/* Don't even check the return value as it's too late. */
|
|
257
579
|
if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1)
|
|
258
580
|
rawmode = 0;
|
|
@@ -288,6 +610,10 @@ static int getCursorPosition(int ifd, int ofd) {
|
|
|
288
610
|
static int getColumns(int ifd, int ofd) {
|
|
289
611
|
struct winsize ws;
|
|
290
612
|
|
|
613
|
+
/* Test mode: use LINENOISE_COLS env var for fixed width. */
|
|
614
|
+
char *cols_env = getenv("LINENOISE_COLS");
|
|
615
|
+
if (cols_env) return atoi(cols_env);
|
|
616
|
+
|
|
291
617
|
if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
|
|
292
618
|
/* ioctl() failed. Try to query the terminal itself. */
|
|
293
619
|
int start, cols;
|
|
@@ -505,16 +831,29 @@ static void abFree(struct abuf *ab) {
|
|
|
505
831
|
}
|
|
506
832
|
|
|
507
833
|
/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
|
|
508
|
-
* to the right of the prompt. */
|
|
509
|
-
void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int
|
|
834
|
+
* to the right of the prompt. Now uses display widths for proper UTF-8. */
|
|
835
|
+
void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int pwidth) {
|
|
510
836
|
char seq[64];
|
|
511
|
-
|
|
837
|
+
size_t bufwidth = utf8StrWidth(l->buf, l->len);
|
|
838
|
+
if (hintsCallback && pwidth + bufwidth < l->cols) {
|
|
512
839
|
int color = -1, bold = 0;
|
|
513
840
|
char *hint = hintsCallback(l->buf,&color,&bold);
|
|
514
841
|
if (hint) {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
842
|
+
size_t hintlen = strlen(hint);
|
|
843
|
+
size_t hintwidth = utf8StrWidth(hint, hintlen);
|
|
844
|
+
size_t hintmaxwidth = l->cols - (pwidth + bufwidth);
|
|
845
|
+
/* Truncate hint to fit, respecting UTF-8 boundaries. */
|
|
846
|
+
if (hintwidth > hintmaxwidth) {
|
|
847
|
+
size_t i = 0, w = 0;
|
|
848
|
+
while (i < hintlen) {
|
|
849
|
+
size_t clen = utf8NextCharLen(hint, i, hintlen);
|
|
850
|
+
int cwidth = utf8SingleCharWidth(hint + i, clen);
|
|
851
|
+
if (w + cwidth > hintmaxwidth) break;
|
|
852
|
+
w += cwidth;
|
|
853
|
+
i += clen;
|
|
854
|
+
}
|
|
855
|
+
hintlen = i;
|
|
856
|
+
}
|
|
518
857
|
if (bold == 1 && color == -1) color = 37;
|
|
519
858
|
if (color != -1 || bold != 0)
|
|
520
859
|
snprintf(seq,64,"\033[%d;%d;49m",bold,color);
|
|
@@ -536,23 +875,44 @@ void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
|
|
|
536
875
|
* cursor position, and number of columns of the terminal.
|
|
537
876
|
*
|
|
538
877
|
* Flags is REFRESH_* macros. The function can just remove the old
|
|
539
|
-
* prompt, just write it, or both.
|
|
878
|
+
* prompt, just write it, or both.
|
|
879
|
+
*
|
|
880
|
+
* This function is UTF-8 aware and uses display widths (not byte counts)
|
|
881
|
+
* for cursor positioning and horizontal scrolling. */
|
|
540
882
|
static void refreshSingleLine(struct linenoiseState *l, int flags) {
|
|
541
883
|
char seq[64];
|
|
542
|
-
size_t
|
|
884
|
+
size_t pwidth = utf8StrWidth(l->prompt, l->plen); /* Prompt display width */
|
|
543
885
|
int fd = l->ofd;
|
|
544
886
|
char *buf = l->buf;
|
|
545
|
-
size_t len = l->len;
|
|
546
|
-
size_t pos = l->pos;
|
|
887
|
+
size_t len = l->len; /* Byte length of buffer to display */
|
|
888
|
+
size_t pos = l->pos; /* Byte position of cursor */
|
|
889
|
+
size_t poscol; /* Display column of cursor */
|
|
890
|
+
size_t lencol; /* Display width of buffer */
|
|
547
891
|
struct abuf ab;
|
|
548
892
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
893
|
+
/* Calculate the display width up to cursor and total display width. */
|
|
894
|
+
poscol = utf8StrWidth(buf, pos);
|
|
895
|
+
lencol = utf8StrWidth(buf, len);
|
|
896
|
+
|
|
897
|
+
/* Scroll the buffer horizontally if cursor is past the right edge.
|
|
898
|
+
* We need to trim full UTF-8 characters from the left until the
|
|
899
|
+
* cursor position fits within the terminal width. */
|
|
900
|
+
while (pwidth + poscol >= l->cols) {
|
|
901
|
+
size_t clen = utf8NextCharLen(buf, 0, len);
|
|
902
|
+
int cwidth = utf8SingleCharWidth(buf, clen);
|
|
903
|
+
buf += clen;
|
|
904
|
+
len -= clen;
|
|
905
|
+
pos -= clen;
|
|
906
|
+
poscol -= cwidth;
|
|
907
|
+
lencol -= cwidth;
|
|
553
908
|
}
|
|
554
|
-
|
|
555
|
-
|
|
909
|
+
|
|
910
|
+
/* Trim from the right if the line still doesn't fit. */
|
|
911
|
+
while (pwidth + lencol > l->cols) {
|
|
912
|
+
size_t clen = utf8PrevCharLen(buf, len);
|
|
913
|
+
int cwidth = utf8SingleCharWidth(buf + len - clen, clen);
|
|
914
|
+
len -= clen;
|
|
915
|
+
lencol -= cwidth;
|
|
556
916
|
}
|
|
557
917
|
|
|
558
918
|
abInit(&ab);
|
|
@@ -562,14 +922,19 @@ static void refreshSingleLine(struct linenoiseState *l, int flags) {
|
|
|
562
922
|
|
|
563
923
|
if (flags & REFRESH_WRITE) {
|
|
564
924
|
/* Write the prompt and the current buffer content */
|
|
565
|
-
abAppend(&ab,l->prompt,
|
|
925
|
+
abAppend(&ab,l->prompt,l->plen);
|
|
566
926
|
if (maskmode == 1) {
|
|
567
|
-
|
|
927
|
+
/* In mask mode, we output one '*' per UTF-8 character, not byte */
|
|
928
|
+
size_t i = 0;
|
|
929
|
+
while (i < len) {
|
|
930
|
+
abAppend(&ab,"*",1);
|
|
931
|
+
i += utf8NextCharLen(buf, i, len);
|
|
932
|
+
}
|
|
568
933
|
} else {
|
|
569
934
|
abAppend(&ab,buf,len);
|
|
570
935
|
}
|
|
571
|
-
/* Show
|
|
572
|
-
refreshShowHints(&ab,l,
|
|
936
|
+
/* Show hints if any. */
|
|
937
|
+
refreshShowHints(&ab,l,pwidth);
|
|
573
938
|
}
|
|
574
939
|
|
|
575
940
|
/* Erase to right */
|
|
@@ -577,8 +942,8 @@ static void refreshSingleLine(struct linenoiseState *l, int flags) {
|
|
|
577
942
|
abAppend(&ab,seq,strlen(seq));
|
|
578
943
|
|
|
579
944
|
if (flags & REFRESH_WRITE) {
|
|
580
|
-
/* Move cursor to original position. */
|
|
581
|
-
snprintf(seq,sizeof(seq),"\r\x1b[%dC", (int)(
|
|
945
|
+
/* Move cursor to original position (using display column, not byte). */
|
|
946
|
+
snprintf(seq,sizeof(seq),"\r\x1b[%dC", (int)(poscol+pwidth));
|
|
582
947
|
abAppend(&ab,seq,strlen(seq));
|
|
583
948
|
}
|
|
584
949
|
|
|
@@ -592,14 +957,18 @@ static void refreshSingleLine(struct linenoiseState *l, int flags) {
|
|
|
592
957
|
* cursor position, and number of columns of the terminal.
|
|
593
958
|
*
|
|
594
959
|
* Flags is REFRESH_* macros. The function can just remove the old
|
|
595
|
-
* prompt, just write it, or both.
|
|
960
|
+
* prompt, just write it, or both.
|
|
961
|
+
*
|
|
962
|
+
* This function is UTF-8 aware and uses display widths for positioning. */
|
|
596
963
|
static void refreshMultiLine(struct linenoiseState *l, int flags) {
|
|
597
964
|
char seq[64];
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
965
|
+
size_t pwidth = utf8StrWidth(l->prompt, l->plen); /* Prompt display width */
|
|
966
|
+
size_t bufwidth = utf8StrWidth(l->buf, l->len); /* Buffer display width */
|
|
967
|
+
size_t poswidth = utf8StrWidth(l->buf, l->pos); /* Cursor display width */
|
|
968
|
+
int rows = (pwidth+bufwidth+l->cols-1)/l->cols; /* rows used by current buf. */
|
|
969
|
+
int rpos = l->oldrpos; /* cursor relative row from previous refresh. */
|
|
601
970
|
int rpos2; /* rpos after refresh. */
|
|
602
|
-
int col; /*
|
|
971
|
+
int col; /* column position, zero-based. */
|
|
603
972
|
int old_rows = l->oldrows;
|
|
604
973
|
int fd = l->ofd, j;
|
|
605
974
|
struct abuf ab;
|
|
@@ -634,22 +1003,26 @@ static void refreshMultiLine(struct linenoiseState *l, int flags) {
|
|
|
634
1003
|
|
|
635
1004
|
if (flags & REFRESH_WRITE) {
|
|
636
1005
|
/* Write the prompt and the current buffer content */
|
|
637
|
-
abAppend(&ab,l->prompt,
|
|
1006
|
+
abAppend(&ab,l->prompt,l->plen);
|
|
638
1007
|
if (maskmode == 1) {
|
|
639
|
-
|
|
640
|
-
|
|
1008
|
+
/* In mask mode, output one '*' per UTF-8 character, not byte */
|
|
1009
|
+
size_t i = 0;
|
|
1010
|
+
while (i < l->len) {
|
|
1011
|
+
abAppend(&ab,"*",1);
|
|
1012
|
+
i += utf8NextCharLen(l->buf, i, l->len);
|
|
1013
|
+
}
|
|
641
1014
|
} else {
|
|
642
1015
|
abAppend(&ab,l->buf,l->len);
|
|
643
1016
|
}
|
|
644
1017
|
|
|
645
|
-
/* Show
|
|
646
|
-
refreshShowHints(&ab,l,
|
|
1018
|
+
/* Show hints if any. */
|
|
1019
|
+
refreshShowHints(&ab,l,pwidth);
|
|
647
1020
|
|
|
648
1021
|
/* If we are at the very end of the screen with our prompt, we need to
|
|
649
1022
|
* emit a newline and move the prompt to the first column. */
|
|
650
1023
|
if (l->pos &&
|
|
651
1024
|
l->pos == l->len &&
|
|
652
|
-
(
|
|
1025
|
+
(poswidth+pwidth) % l->cols == 0)
|
|
653
1026
|
{
|
|
654
1027
|
lndebug("<newline>");
|
|
655
1028
|
abAppend(&ab,"\n",1);
|
|
@@ -660,10 +1033,10 @@ static void refreshMultiLine(struct linenoiseState *l, int flags) {
|
|
|
660
1033
|
}
|
|
661
1034
|
|
|
662
1035
|
/* Move cursor to right position. */
|
|
663
|
-
rpos2 = (
|
|
1036
|
+
rpos2 = (pwidth+poswidth+l->cols)/l->cols; /* Current cursor relative row */
|
|
664
1037
|
lndebug("rpos2 %d", rpos2);
|
|
665
1038
|
|
|
666
|
-
/* Go up till we reach the expected
|
|
1039
|
+
/* Go up till we reach the expected position. */
|
|
667
1040
|
if (rows-rpos2 > 0) {
|
|
668
1041
|
lndebug("go-up %d", rows-rpos2);
|
|
669
1042
|
snprintf(seq,64,"\x1b[%dA", rows-rpos2);
|
|
@@ -671,7 +1044,7 @@ static void refreshMultiLine(struct linenoiseState *l, int flags) {
|
|
|
671
1044
|
}
|
|
672
1045
|
|
|
673
1046
|
/* Set column. */
|
|
674
|
-
col = (
|
|
1047
|
+
col = (pwidth+poswidth) % l->cols;
|
|
675
1048
|
lndebug("set col %d", 1+col);
|
|
676
1049
|
if (col)
|
|
677
1050
|
snprintf(seq,64,"\r\x1b[%dC", col);
|
|
@@ -682,6 +1055,7 @@ static void refreshMultiLine(struct linenoiseState *l, int flags) {
|
|
|
682
1055
|
|
|
683
1056
|
lndebug("\n");
|
|
684
1057
|
l->oldpos = l->pos;
|
|
1058
|
+
if (flags & REFRESH_WRITE) l->oldrpos = rpos2;
|
|
685
1059
|
|
|
686
1060
|
if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
|
|
687
1061
|
abFree(&ab);
|
|
@@ -718,29 +1092,37 @@ void linenoiseShow(struct linenoiseState *l) {
|
|
|
718
1092
|
}
|
|
719
1093
|
}
|
|
720
1094
|
|
|
721
|
-
/* Insert the character 'c' at cursor current position.
|
|
1095
|
+
/* Insert the character(s) 'c' of length 'clen' at cursor current position.
|
|
1096
|
+
* This handles both single-byte ASCII and multi-byte UTF-8 sequences.
|
|
722
1097
|
*
|
|
723
1098
|
* On error writing to the terminal -1 is returned, otherwise 0. */
|
|
724
|
-
int linenoiseEditInsert(struct linenoiseState *l, char c) {
|
|
725
|
-
if (l->len
|
|
1099
|
+
int linenoiseEditInsert(struct linenoiseState *l, const char *c, size_t clen) {
|
|
1100
|
+
if (l->len + clen <= l->buflen) {
|
|
726
1101
|
if (l->len == l->pos) {
|
|
727
|
-
|
|
728
|
-
l->pos
|
|
729
|
-
l->
|
|
1102
|
+
/* Append at end of line. */
|
|
1103
|
+
memcpy(l->buf+l->pos, c, clen);
|
|
1104
|
+
l->pos += clen;
|
|
1105
|
+
l->len += clen;
|
|
730
1106
|
l->buf[l->len] = '\0';
|
|
731
|
-
if ((!mlmode &&
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1107
|
+
if ((!mlmode &&
|
|
1108
|
+
utf8StrWidth(l->prompt,l->plen)+utf8StrWidth(l->buf,l->len) < l->cols &&
|
|
1109
|
+
!hintsCallback)) {
|
|
1110
|
+
/* Avoid a full update of the line in the trivial case:
|
|
1111
|
+
* single-width char, no hints, fits in one line. */
|
|
1112
|
+
if (maskmode == 1) {
|
|
1113
|
+
if (write(l->ofd,"*",1) == -1) return -1;
|
|
1114
|
+
} else {
|
|
1115
|
+
if (write(l->ofd,c,clen) == -1) return -1;
|
|
1116
|
+
}
|
|
736
1117
|
} else {
|
|
737
1118
|
refreshLine(l);
|
|
738
1119
|
}
|
|
739
1120
|
} else {
|
|
740
|
-
|
|
741
|
-
l->buf
|
|
742
|
-
l->
|
|
743
|
-
l->
|
|
1121
|
+
/* Insert in the middle of the line. */
|
|
1122
|
+
memmove(l->buf+l->pos+clen, l->buf+l->pos, l->len-l->pos);
|
|
1123
|
+
memcpy(l->buf+l->pos, c, clen);
|
|
1124
|
+
l->len += clen;
|
|
1125
|
+
l->pos += clen;
|
|
744
1126
|
l->buf[l->len] = '\0';
|
|
745
1127
|
refreshLine(l);
|
|
746
1128
|
}
|
|
@@ -748,18 +1130,18 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) {
|
|
|
748
1130
|
return 0;
|
|
749
1131
|
}
|
|
750
1132
|
|
|
751
|
-
/* Move cursor on the left. */
|
|
1133
|
+
/* Move cursor on the left. Moves by one UTF-8 character, not byte. */
|
|
752
1134
|
void linenoiseEditMoveLeft(struct linenoiseState *l) {
|
|
753
1135
|
if (l->pos > 0) {
|
|
754
|
-
l->pos
|
|
1136
|
+
l->pos -= utf8PrevCharLen(l->buf, l->pos);
|
|
755
1137
|
refreshLine(l);
|
|
756
1138
|
}
|
|
757
1139
|
}
|
|
758
1140
|
|
|
759
|
-
/* Move cursor on the right. */
|
|
1141
|
+
/* Move cursor on the right. Moves by one UTF-8 character, not byte. */
|
|
760
1142
|
void linenoiseEditMoveRight(struct linenoiseState *l) {
|
|
761
1143
|
if (l->pos != l->len) {
|
|
762
|
-
l->pos
|
|
1144
|
+
l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
|
|
763
1145
|
refreshLine(l);
|
|
764
1146
|
}
|
|
765
1147
|
}
|
|
@@ -807,39 +1189,44 @@ void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) {
|
|
|
807
1189
|
}
|
|
808
1190
|
|
|
809
1191
|
/* Delete the character at the right of the cursor without altering the cursor
|
|
810
|
-
* position. Basically this is what happens with the "Delete" keyboard key.
|
|
1192
|
+
* position. Basically this is what happens with the "Delete" keyboard key.
|
|
1193
|
+
* Now handles multi-byte UTF-8 characters. */
|
|
811
1194
|
void linenoiseEditDelete(struct linenoiseState *l) {
|
|
812
1195
|
if (l->len > 0 && l->pos < l->len) {
|
|
813
|
-
|
|
814
|
-
l->len
|
|
1196
|
+
size_t clen = utf8NextCharLen(l->buf, l->pos, l->len);
|
|
1197
|
+
memmove(l->buf+l->pos, l->buf+l->pos+clen, l->len-l->pos-clen);
|
|
1198
|
+
l->len -= clen;
|
|
815
1199
|
l->buf[l->len] = '\0';
|
|
816
1200
|
refreshLine(l);
|
|
817
1201
|
}
|
|
818
1202
|
}
|
|
819
1203
|
|
|
820
|
-
/* Backspace implementation. */
|
|
1204
|
+
/* Backspace implementation. Deletes the UTF-8 character before the cursor. */
|
|
821
1205
|
void linenoiseEditBackspace(struct linenoiseState *l) {
|
|
822
1206
|
if (l->pos > 0 && l->len > 0) {
|
|
823
|
-
|
|
824
|
-
l->pos
|
|
825
|
-
l->
|
|
1207
|
+
size_t clen = utf8PrevCharLen(l->buf, l->pos);
|
|
1208
|
+
memmove(l->buf+l->pos-clen, l->buf+l->pos, l->len-l->pos);
|
|
1209
|
+
l->pos -= clen;
|
|
1210
|
+
l->len -= clen;
|
|
826
1211
|
l->buf[l->len] = '\0';
|
|
827
1212
|
refreshLine(l);
|
|
828
1213
|
}
|
|
829
1214
|
}
|
|
830
1215
|
|
|
831
|
-
/* Delete the
|
|
832
|
-
* current word. */
|
|
1216
|
+
/* Delete the previous word, maintaining the cursor at the start of the
|
|
1217
|
+
* current word. Handles UTF-8 by moving character-by-character. */
|
|
833
1218
|
void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
|
|
834
1219
|
size_t old_pos = l->pos;
|
|
835
1220
|
size_t diff;
|
|
836
1221
|
|
|
1222
|
+
/* Skip spaces before the word (move backwards by UTF-8 chars). */
|
|
837
1223
|
while (l->pos > 0 && l->buf[l->pos-1] == ' ')
|
|
838
|
-
l->pos
|
|
1224
|
+
l->pos -= utf8PrevCharLen(l->buf, l->pos);
|
|
1225
|
+
/* Skip non-space characters (move backwards by UTF-8 chars). */
|
|
839
1226
|
while (l->pos > 0 && l->buf[l->pos-1] != ' ')
|
|
840
|
-
l->pos
|
|
1227
|
+
l->pos -= utf8PrevCharLen(l->buf, l->pos);
|
|
841
1228
|
diff = old_pos - l->pos;
|
|
842
|
-
memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1);
|
|
1229
|
+
memmove(l->buf+l->pos, l->buf+old_pos, l->len-old_pos+1);
|
|
843
1230
|
l->len -= diff;
|
|
844
1231
|
refreshLine(l);
|
|
845
1232
|
}
|
|
@@ -886,6 +1273,7 @@ int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, ch
|
|
|
886
1273
|
|
|
887
1274
|
l->cols = getColumns(stdin_fd, stdout_fd);
|
|
888
1275
|
l->oldrows = 0;
|
|
1276
|
+
l->oldrpos = 1; /* Cursor starts on row 1. */
|
|
889
1277
|
l->history_index = 0;
|
|
890
1278
|
|
|
891
1279
|
/* Buffer starts empty. */
|
|
@@ -895,7 +1283,7 @@ int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, ch
|
|
|
895
1283
|
/* If stdin is not a tty, stop here with the initialization. We
|
|
896
1284
|
* will actually just read a line from standard input in blocking
|
|
897
1285
|
* mode later, in linenoiseEditFeed(). */
|
|
898
|
-
if (!isatty(l->ifd)) return 0;
|
|
1286
|
+
if (!isatty(l->ifd) && !getenv("LINENOISE_ASSUME_TTY")) return 0;
|
|
899
1287
|
|
|
900
1288
|
/* The latest history entry is always our current buffer, that
|
|
901
1289
|
* initially is just an empty string. */
|
|
@@ -928,14 +1316,18 @@ char *linenoiseEditMore = "If you see this, you are misusing the API: when linen
|
|
|
928
1316
|
char *linenoiseEditFeed(struct linenoiseState *l) {
|
|
929
1317
|
/* Not a TTY, pass control to line reading without character
|
|
930
1318
|
* count limits. */
|
|
931
|
-
if (!isatty(l->ifd)) return linenoiseNoTTY();
|
|
1319
|
+
if (!isatty(l->ifd) && !getenv("LINENOISE_ASSUME_TTY")) return linenoiseNoTTY();
|
|
932
1320
|
|
|
933
1321
|
char c;
|
|
934
1322
|
int nread;
|
|
935
1323
|
char seq[3];
|
|
936
1324
|
|
|
937
1325
|
nread = read(l->ifd,&c,1);
|
|
938
|
-
if (nread
|
|
1326
|
+
if (nread < 0) {
|
|
1327
|
+
return (errno == EAGAIN || errno == EWOULDBLOCK) ? linenoiseEditMore : NULL;
|
|
1328
|
+
} else if (nread == 0) {
|
|
1329
|
+
return NULL;
|
|
1330
|
+
}
|
|
939
1331
|
|
|
940
1332
|
/* Only autocomplete when the callback is set. It returns < 0 when
|
|
941
1333
|
* there was an error reading from fd. Otherwise it will return the
|
|
@@ -981,11 +1373,17 @@ char *linenoiseEditFeed(struct linenoiseState *l) {
|
|
|
981
1373
|
}
|
|
982
1374
|
break;
|
|
983
1375
|
case CTRL_T: /* ctrl-t, swaps current character with previous. */
|
|
1376
|
+
/* Handle UTF-8: swap the two UTF-8 characters around cursor. */
|
|
984
1377
|
if (l->pos > 0 && l->pos < l->len) {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
l->buf
|
|
988
|
-
|
|
1378
|
+
char tmp[32];
|
|
1379
|
+
size_t prevlen = utf8PrevCharLen(l->buf, l->pos);
|
|
1380
|
+
size_t currlen = utf8NextCharLen(l->buf, l->pos, l->len);
|
|
1381
|
+
size_t prevstart = l->pos - prevlen;
|
|
1382
|
+
/* Copy current char to tmp, move previous char right, paste tmp. */
|
|
1383
|
+
memcpy(tmp, l->buf + l->pos, currlen);
|
|
1384
|
+
memmove(l->buf + prevstart + currlen, l->buf + prevstart, prevlen);
|
|
1385
|
+
memcpy(l->buf + prevstart, tmp, currlen);
|
|
1386
|
+
if (l->pos + currlen <= l->len) l->pos += currlen;
|
|
989
1387
|
refreshLine(l);
|
|
990
1388
|
}
|
|
991
1389
|
break;
|
|
@@ -1057,7 +1455,22 @@ char *linenoiseEditFeed(struct linenoiseState *l) {
|
|
|
1057
1455
|
}
|
|
1058
1456
|
break;
|
|
1059
1457
|
default:
|
|
1060
|
-
|
|
1458
|
+
/* Handle UTF-8 multi-byte sequences. When we receive the first byte
|
|
1459
|
+
* of a multi-byte UTF-8 character, read the remaining bytes to
|
|
1460
|
+
* complete the sequence before inserting. */
|
|
1461
|
+
{
|
|
1462
|
+
char utf8[4];
|
|
1463
|
+
int utf8len = utf8ByteLen(c);
|
|
1464
|
+
utf8[0] = c;
|
|
1465
|
+
if (utf8len > 1) {
|
|
1466
|
+
/* Read remaining bytes of the UTF-8 sequence. */
|
|
1467
|
+
int i;
|
|
1468
|
+
for (i = 1; i < utf8len; i++) {
|
|
1469
|
+
if (read(l->ifd, utf8+i, 1) != 1) break;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
if (linenoiseEditInsert(l, utf8, utf8len)) return NULL;
|
|
1473
|
+
}
|
|
1061
1474
|
break;
|
|
1062
1475
|
case CTRL_U: /* Ctrl+u, delete the whole line. */
|
|
1063
1476
|
l->buf[0] = '\0';
|
|
@@ -1091,7 +1504,7 @@ char *linenoiseEditFeed(struct linenoiseState *l) {
|
|
|
1091
1504
|
* returns something different than NULL. At this point the user input
|
|
1092
1505
|
* is in the buffer, and we can restore the terminal in normal mode. */
|
|
1093
1506
|
void linenoiseEditStop(struct linenoiseState *l) {
|
|
1094
|
-
if (!isatty(l->ifd)) return;
|
|
1507
|
+
if (!isatty(l->ifd) && !getenv("LINENOISE_ASSUME_TTY")) return;
|
|
1095
1508
|
disableRawMode(l->ifd);
|
|
1096
1509
|
printf("\n");
|
|
1097
1510
|
}
|
|
@@ -1189,7 +1602,7 @@ static char *linenoiseNoTTY(void) {
|
|
|
1189
1602
|
char *linenoise(const char *prompt) {
|
|
1190
1603
|
char buf[LINENOISE_MAX_LINE];
|
|
1191
1604
|
|
|
1192
|
-
if (!isatty(STDIN_FILENO)) {
|
|
1605
|
+
if (!isatty(STDIN_FILENO) && !getenv("LINENOISE_ASSUME_TTY")) {
|
|
1193
1606
|
/* Not a tty: read from file / pipe. In this mode we don't want any
|
|
1194
1607
|
* limit to the line size, so we call a function to handle that. */
|
|
1195
1608
|
return linenoiseNoTTY();
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/* linenoise.h -- VERSION 1.0
|
|
2
|
+
*
|
|
3
|
+
* Guerrilla line editing library against the idea that a line editing lib
|
|
4
|
+
* needs to be 20,000 lines of C code.
|
|
5
|
+
*
|
|
6
|
+
* See linenoise.c for more information.
|
|
7
|
+
*
|
|
8
|
+
* ------------------------------------------------------------------------
|
|
9
|
+
*
|
|
10
|
+
* Copyright (c) 2010-2023, Salvatore Sanfilippo <antirez at gmail dot com>
|
|
11
|
+
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
|
12
|
+
*
|
|
13
|
+
* All rights reserved.
|
|
14
|
+
*
|
|
15
|
+
* Redistribution and use in source and binary forms, with or without
|
|
16
|
+
* modification, are permitted provided that the following conditions are
|
|
17
|
+
* met:
|
|
18
|
+
*
|
|
19
|
+
* * Redistributions of source code must retain the above copyright
|
|
20
|
+
* notice, this list of conditions and the following disclaimer.
|
|
21
|
+
*
|
|
22
|
+
* * Redistributions in binary form must reproduce the above copyright
|
|
23
|
+
* notice, this list of conditions and the following disclaimer in the
|
|
24
|
+
* documentation and/or other materials provided with the distribution.
|
|
25
|
+
*
|
|
26
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
27
|
+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
28
|
+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
29
|
+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
30
|
+
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
31
|
+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
32
|
+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
33
|
+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
34
|
+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
35
|
+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
36
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
#ifndef __LINENOISE_H
|
|
40
|
+
#define __LINENOISE_H
|
|
41
|
+
|
|
42
|
+
#ifdef __cplusplus
|
|
43
|
+
extern "C" {
|
|
44
|
+
#endif
|
|
45
|
+
|
|
46
|
+
#include <stddef.h> /* For size_t. */
|
|
47
|
+
|
|
48
|
+
extern char *linenoiseEditMore;
|
|
49
|
+
|
|
50
|
+
/* The linenoiseState structure represents the state during line editing.
|
|
51
|
+
* We pass this state to functions implementing specific editing
|
|
52
|
+
* functionalities. */
|
|
53
|
+
struct linenoiseState {
|
|
54
|
+
int in_completion; /* The user pressed TAB and we are now in completion
|
|
55
|
+
* mode, so input is handled by completeLine(). */
|
|
56
|
+
size_t completion_idx; /* Index of next completion to propose. */
|
|
57
|
+
int ifd; /* Terminal stdin file descriptor. */
|
|
58
|
+
int ofd; /* Terminal stdout file descriptor. */
|
|
59
|
+
char *buf; /* Edited line buffer. */
|
|
60
|
+
size_t buflen; /* Edited line buffer size. */
|
|
61
|
+
const char *prompt; /* Prompt to display. */
|
|
62
|
+
size_t plen; /* Prompt length. */
|
|
63
|
+
size_t pos; /* Current cursor position. */
|
|
64
|
+
size_t oldpos; /* Previous refresh cursor position. */
|
|
65
|
+
size_t len; /* Current edited line length. */
|
|
66
|
+
size_t cols; /* Number of columns in terminal. */
|
|
67
|
+
size_t oldrows; /* Rows used by last refrehsed line (multiline mode) */
|
|
68
|
+
int oldrpos; /* Cursor row from last refresh (for multiline clearing). */
|
|
69
|
+
int history_index; /* The history index we are currently editing. */
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
typedef struct linenoiseCompletions {
|
|
73
|
+
size_t len;
|
|
74
|
+
char **cvec;
|
|
75
|
+
} linenoiseCompletions;
|
|
76
|
+
|
|
77
|
+
/* Non blocking API. */
|
|
78
|
+
int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt);
|
|
79
|
+
char *linenoiseEditFeed(struct linenoiseState *l);
|
|
80
|
+
void linenoiseEditStop(struct linenoiseState *l);
|
|
81
|
+
void linenoiseHide(struct linenoiseState *l);
|
|
82
|
+
void linenoiseShow(struct linenoiseState *l);
|
|
83
|
+
|
|
84
|
+
/* Blocking API. */
|
|
85
|
+
char *linenoise(const char *prompt);
|
|
86
|
+
void linenoiseFree(void *ptr);
|
|
87
|
+
|
|
88
|
+
/* Completion API. */
|
|
89
|
+
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
|
|
90
|
+
typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
|
|
91
|
+
typedef void(linenoiseFreeHintsCallback)(void *);
|
|
92
|
+
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
|
|
93
|
+
void linenoiseSetHintsCallback(linenoiseHintsCallback *);
|
|
94
|
+
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
|
|
95
|
+
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
|
|
96
|
+
|
|
97
|
+
/* History API. */
|
|
98
|
+
int linenoiseHistoryAdd(const char *line);
|
|
99
|
+
int linenoiseHistorySetMaxLen(int len);
|
|
100
|
+
int linenoiseHistorySave(const char *filename);
|
|
101
|
+
int linenoiseHistoryLoad(const char *filename);
|
|
102
|
+
|
|
103
|
+
/* Other utilities. */
|
|
104
|
+
void linenoiseClearScreen(void);
|
|
105
|
+
void linenoiseSetMultiLine(int ml);
|
|
106
|
+
void linenoisePrintKeyCodes(void);
|
|
107
|
+
void linenoiseMaskModeEnable(void);
|
|
108
|
+
void linenoiseMaskModeDisable(void);
|
|
109
|
+
|
|
110
|
+
#ifdef __cplusplus
|
|
111
|
+
}
|
|
112
|
+
#endif
|
|
113
|
+
|
|
114
|
+
#endif /* __LINENOISE_H */
|
package/linenoise.h
CHANGED
|
@@ -1,113 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
* See linenoise.c for more information.
|
|
7
|
-
*
|
|
8
|
-
* ------------------------------------------------------------------------
|
|
9
|
-
*
|
|
10
|
-
* Copyright (c) 2010-2023, Salvatore Sanfilippo <antirez at gmail dot com>
|
|
11
|
-
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
|
12
|
-
*
|
|
13
|
-
* All rights reserved.
|
|
14
|
-
*
|
|
15
|
-
* Redistribution and use in source and binary forms, with or without
|
|
16
|
-
* modification, are permitted provided that the following conditions are
|
|
17
|
-
* met:
|
|
18
|
-
*
|
|
19
|
-
* * Redistributions of source code must retain the above copyright
|
|
20
|
-
* notice, this list of conditions and the following disclaimer.
|
|
21
|
-
*
|
|
22
|
-
* * Redistributions in binary form must reproduce the above copyright
|
|
23
|
-
* notice, this list of conditions and the following disclaimer in the
|
|
24
|
-
* documentation and/or other materials provided with the distribution.
|
|
25
|
-
*
|
|
26
|
-
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
27
|
-
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
28
|
-
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
29
|
-
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
30
|
-
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
31
|
-
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
32
|
-
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
33
|
-
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
34
|
-
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
35
|
-
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
36
|
-
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
#ifndef __LINENOISE_H
|
|
40
|
-
#define __LINENOISE_H
|
|
41
|
-
|
|
42
|
-
#ifdef __cplusplus
|
|
43
|
-
extern "C" {
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "linenoise/linenoise.h"
|
|
3
|
+
#ifdef LINENOISE_IMPLEMENTATION
|
|
4
|
+
#include "linenoise/linenoise.c"
|
|
44
5
|
#endif
|
|
45
|
-
|
|
46
|
-
#include <stddef.h> /* For size_t. */
|
|
47
|
-
|
|
48
|
-
extern char *linenoiseEditMore;
|
|
49
|
-
|
|
50
|
-
/* The linenoiseState structure represents the state during line editing.
|
|
51
|
-
* We pass this state to functions implementing specific editing
|
|
52
|
-
* functionalities. */
|
|
53
|
-
struct linenoiseState {
|
|
54
|
-
int in_completion; /* The user pressed TAB and we are now in completion
|
|
55
|
-
* mode, so input is handled by completeLine(). */
|
|
56
|
-
size_t completion_idx; /* Index of next completion to propose. */
|
|
57
|
-
int ifd; /* Terminal stdin file descriptor. */
|
|
58
|
-
int ofd; /* Terminal stdout file descriptor. */
|
|
59
|
-
char *buf; /* Edited line buffer. */
|
|
60
|
-
size_t buflen; /* Edited line buffer size. */
|
|
61
|
-
const char *prompt; /* Prompt to display. */
|
|
62
|
-
size_t plen; /* Prompt length. */
|
|
63
|
-
size_t pos; /* Current cursor position. */
|
|
64
|
-
size_t oldpos; /* Previous refresh cursor position. */
|
|
65
|
-
size_t len; /* Current edited line length. */
|
|
66
|
-
size_t cols; /* Number of columns in terminal. */
|
|
67
|
-
size_t oldrows; /* Rows used by last refrehsed line (multiline mode) */
|
|
68
|
-
int history_index; /* The history index we are currently editing. */
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
typedef struct linenoiseCompletions {
|
|
72
|
-
size_t len;
|
|
73
|
-
char **cvec;
|
|
74
|
-
} linenoiseCompletions;
|
|
75
|
-
|
|
76
|
-
/* Non blocking API. */
|
|
77
|
-
int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt);
|
|
78
|
-
char *linenoiseEditFeed(struct linenoiseState *l);
|
|
79
|
-
void linenoiseEditStop(struct linenoiseState *l);
|
|
80
|
-
void linenoiseHide(struct linenoiseState *l);
|
|
81
|
-
void linenoiseShow(struct linenoiseState *l);
|
|
82
|
-
|
|
83
|
-
/* Blocking API. */
|
|
84
|
-
char *linenoise(const char *prompt);
|
|
85
|
-
void linenoiseFree(void *ptr);
|
|
86
|
-
|
|
87
|
-
/* Completion API. */
|
|
88
|
-
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
|
|
89
|
-
typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
|
|
90
|
-
typedef void(linenoiseFreeHintsCallback)(void *);
|
|
91
|
-
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
|
|
92
|
-
void linenoiseSetHintsCallback(linenoiseHintsCallback *);
|
|
93
|
-
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
|
|
94
|
-
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
|
|
95
|
-
|
|
96
|
-
/* History API. */
|
|
97
|
-
int linenoiseHistoryAdd(const char *line);
|
|
98
|
-
int linenoiseHistorySetMaxLen(int len);
|
|
99
|
-
int linenoiseHistorySave(const char *filename);
|
|
100
|
-
int linenoiseHistoryLoad(const char *filename);
|
|
101
|
-
|
|
102
|
-
/* Other utilities. */
|
|
103
|
-
void linenoiseClearScreen(void);
|
|
104
|
-
void linenoiseSetMultiLine(int ml);
|
|
105
|
-
void linenoisePrintKeyCodes(void);
|
|
106
|
-
void linenoiseMaskModeEnable(void);
|
|
107
|
-
void linenoiseMaskModeDisable(void);
|
|
108
|
-
|
|
109
|
-
#ifdef __cplusplus
|
|
110
|
-
}
|
|
111
|
-
#endif
|
|
112
|
-
|
|
113
|
-
#endif /* __LINENOISE_H */
|
package/package.json
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linenoise.c",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.2026.2",
|
|
4
4
|
"description": "A small self-contained alternative to readline and libedit; Salvatore Sanfilippo (2010).",
|
|
5
|
-
"keywords": [
|
|
6
|
-
|
|
5
|
+
"keywords": [
|
|
6
|
+
"c",
|
|
7
|
+
"library",
|
|
8
|
+
"readline",
|
|
9
|
+
"libedit",
|
|
10
|
+
"single-line",
|
|
11
|
+
"multi-line",
|
|
12
|
+
"editing",
|
|
13
|
+
"history",
|
|
14
|
+
"completion",
|
|
15
|
+
"terminal"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/nodef/linenoise.c#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/nodef/linenoise.c/issues"
|
|
20
|
+
},
|
|
7
21
|
"repository": {
|
|
8
22
|
"type": "git",
|
|
9
23
|
"url": "git+https://github.com/nodef/linenoise.c.git"
|
|
10
24
|
},
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
25
|
+
"license": "BSD-2-Clause",
|
|
26
|
+
"author": "wolfram77@gmail.com",
|
|
27
|
+
"main": "linenoise.h",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "bash build.sh test"
|
|
30
|
+
}
|
|
15
31
|
}
|