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.
@@ -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. By [Salvatore Sanfilippo](https://github.com/antirez).
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
- * About ~850 lines (comments and spaces excluded) of BSD license source code.
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 UTF-8 support, Windows support and other features. Uses C++ instead of C as development language.
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://raw.githubusercontent.com/qb40/designs/gh-pages/0/image/11.png)](https://wolfram77.github.io)<br>
420
+ [![SRC](https://img.shields.io/badge/src-repo-green?logo=Org)](https://github.com/antirez/linenoise)
375
421
  [![ORG](https://img.shields.io/badge/org-nodef-green?logo=Org)](https://nodef.github.io)
376
422
  ![](https://ga-beacon.deno.dev/G-RC63DPBH3P:SH3Eq-NoQ9mwgYeHWxu7cw/github.com/nodef/linenoise.c)
@@ -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 plen) {
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
- if (hintsCallback && plen+l->len < l->cols) {
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
- int hintlen = strlen(hint);
516
- int hintmaxlen = l->cols-(plen+l->len);
517
- if (hintlen > hintmaxlen) hintlen = hintmaxlen;
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 plen = strlen(l->prompt);
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
- while((plen+pos) >= l->cols) {
550
- buf++;
551
- len--;
552
- pos--;
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
- while (plen+len > l->cols) {
555
- len--;
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,strlen(l->prompt));
925
+ abAppend(&ab,l->prompt,l->plen);
566
926
  if (maskmode == 1) {
567
- while (len--) abAppend(&ab,"*",1);
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 hits if any. */
572
- refreshShowHints(&ab,l,plen);
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)(pos+plen));
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
- int plen = strlen(l->prompt);
599
- int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */
600
- int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */
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; /* colum position, zero-based. */
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,strlen(l->prompt));
1006
+ abAppend(&ab,l->prompt,l->plen);
638
1007
  if (maskmode == 1) {
639
- unsigned int i;
640
- for (i = 0; i < l->len; i++) abAppend(&ab,"*",1);
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 hits if any. */
646
- refreshShowHints(&ab,l,plen);
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
- (l->pos+plen) % l->cols == 0)
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 = (plen+l->pos+l->cols)/l->cols; /* Current cursor relative row */
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 positon. */
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 = (plen+(int)l->pos) % (int)l->cols;
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 < l->buflen) {
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
- l->buf[l->pos] = c;
728
- l->pos++;
729
- l->len++;
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 && l->plen+l->len < l->cols && !hintsCallback)) {
732
- /* Avoid a full update of the line in the
733
- * trivial case. */
734
- char d = (maskmode==1) ? '*' : c;
735
- if (write(l->ofd,&d,1) == -1) return -1;
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
- memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos);
741
- l->buf[l->pos] = c;
742
- l->len++;
743
- l->pos++;
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
- memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1);
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
- memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos);
824
- l->pos--;
825
- l->len--;
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 previosu word, maintaining the cursor at the start of 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 <= 0) return NULL;
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
- int aux = l->buf[l->pos-1];
986
- l->buf[l->pos-1] = l->buf[l->pos];
987
- l->buf[l->pos] = aux;
988
- if (l->pos != l->len-1) l->pos++;
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
- if (linenoiseEditInsert(l,c)) return NULL;
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
- /* 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" {
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": "1.2025.9",
3
+ "version": "2.2026.2",
4
4
  "description": "A small self-contained alternative to readline and libedit; Salvatore Sanfilippo (2010).",
5
- "keywords": ["c", "library", "readline", "libedit", "linenoise"],
6
- "license": "BSD-2-Clause",
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
- "bugs": {
12
- "url": "https://github.com/nodef/linenoise.c/issues"
13
- },
14
- "homepage": "https://github.com/nodef/linenoise.c#readme"
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
  }
package/Makefile DELETED
@@ -1,7 +0,0 @@
1
- linenoise_example: linenoise.h linenoise.c
2
-
3
- linenoise_example: linenoise.c example.c
4
- $(CC) -Wall -W -Os -g -o linenoise_example linenoise.c example.c
5
-
6
- clean:
7
- rm -f linenoise_example