@fugood/llama.node 1.4.6 → 1.4.8

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.
Files changed (71) hide show
  1. package/lib/binding.ts +8 -0
  2. package/package.json +15 -15
  3. package/scripts/llama.cpp.patch +25 -26
  4. package/src/LlamaContext.cpp +2 -2
  5. package/src/llama.cpp/common/CMakeLists.txt +2 -0
  6. package/src/llama.cpp/common/arg.cpp +364 -193
  7. package/src/llama.cpp/common/arg.h +43 -2
  8. package/src/llama.cpp/common/chat-parser-xml-toolcall.cpp +36 -18
  9. package/src/llama.cpp/common/chat-parser-xml-toolcall.h +1 -1
  10. package/src/llama.cpp/common/chat-parser.cpp +3 -2
  11. package/src/llama.cpp/common/chat-peg-parser.cpp +16 -2
  12. package/src/llama.cpp/common/chat.cpp +272 -0
  13. package/src/llama.cpp/common/common.cpp +130 -67
  14. package/src/llama.cpp/common/common.h +40 -16
  15. package/src/llama.cpp/common/console.cpp +680 -47
  16. package/src/llama.cpp/common/console.h +30 -8
  17. package/src/llama.cpp/common/download.cpp +69 -25
  18. package/src/llama.cpp/common/json-schema-to-grammar.cpp +132 -3
  19. package/src/llama.cpp/common/json-schema-to-grammar.h +20 -0
  20. package/src/llama.cpp/common/log.cpp +5 -0
  21. package/src/llama.cpp/common/log.h +1 -0
  22. package/src/llama.cpp/common/peg-parser.cpp +1 -1
  23. package/src/llama.cpp/common/preset.cpp +206 -0
  24. package/src/llama.cpp/common/preset.h +32 -0
  25. package/src/llama.cpp/common/sampling.cpp +91 -92
  26. package/src/llama.cpp/common/sampling.h +11 -6
  27. package/src/llama.cpp/common/speculative.cpp +1 -1
  28. package/src/llama.cpp/ggml/CMakeLists.txt +5 -0
  29. package/src/llama.cpp/ggml/include/ggml-alloc.h +9 -0
  30. package/src/llama.cpp/ggml/include/ggml-backend.h +1 -0
  31. package/src/llama.cpp/ggml/include/ggml-cpu.h +1 -0
  32. package/src/llama.cpp/ggml/include/ggml.h +7 -8
  33. package/src/llama.cpp/ggml/src/CMakeLists.txt +3 -0
  34. package/src/llama.cpp/ggml/src/ggml-cpu/CMakeLists.txt +3 -0
  35. package/src/llama.cpp/ggml/src/ggml-cpu/arch/arm/repack.cpp +2 -0
  36. package/src/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c +69 -39
  37. package/src/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.cpp +4 -0
  38. package/src/llama.cpp/ggml/src/ggml-cpu/repack.cpp +2 -1
  39. package/src/llama.cpp/include/llama.h +18 -1
  40. package/src/llama.cpp/src/CMakeLists.txt +2 -1
  41. package/src/llama.cpp/src/llama-arch.cpp +1890 -2248
  42. package/src/llama.cpp/src/llama-arch.h +9 -2
  43. package/src/llama.cpp/src/llama-batch.cpp +12 -2
  44. package/src/llama.cpp/src/llama-batch.h +4 -2
  45. package/src/llama.cpp/src/llama-context.cpp +99 -29
  46. package/src/llama.cpp/src/llama-context.h +9 -3
  47. package/src/llama.cpp/src/llama-grammar.cpp +233 -33
  48. package/src/llama.cpp/src/llama-grammar.h +20 -1
  49. package/src/llama.cpp/src/llama-graph.cpp +85 -17
  50. package/src/llama.cpp/src/llama-graph.h +17 -4
  51. package/src/llama.cpp/src/llama-hparams.cpp +6 -0
  52. package/src/llama.cpp/src/llama-hparams.h +5 -1
  53. package/src/llama.cpp/src/llama-impl.cpp +4 -0
  54. package/src/llama.cpp/src/llama-kv-cache.cpp +90 -42
  55. package/src/llama.cpp/src/llama-kv-cache.h +19 -2
  56. package/src/llama.cpp/src/llama-memory-hybrid.cpp +1 -1
  57. package/src/llama.cpp/src/llama-model-loader.cpp +2 -0
  58. package/src/llama.cpp/src/llama-model-loader.h +2 -0
  59. package/src/llama.cpp/src/llama-model.cpp +123 -52
  60. package/src/llama.cpp/src/llama-model.h +1 -0
  61. package/src/llama.cpp/src/llama-quant.cpp +1 -1
  62. package/src/llama.cpp/src/llama-vocab.cpp +2 -1
  63. package/src/llama.cpp/src/llama.cpp +675 -1
  64. package/src/llama.cpp/src/models/deepseek2.cpp +9 -5
  65. package/src/llama.cpp/src/models/{gemma3-iswa.cpp → gemma3.cpp} +30 -5
  66. package/src/llama.cpp/src/models/glm4-moe.cpp +28 -11
  67. package/src/llama.cpp/src/models/glm4.cpp +27 -4
  68. package/src/llama.cpp/src/models/models.h +8 -7
  69. package/src/llama.cpp/src/models/nemotron-h.cpp +35 -6
  70. package/src/llama.cpp/src/models/qwen2.cpp +12 -3
  71. package/src/llama.cpp/src/models/qwen3next.cpp +81 -266
@@ -1,6 +1,16 @@
1
1
  #include "console.h"
2
+ #include "log.h"
2
3
  #include <vector>
3
4
  #include <iostream>
5
+ #include <cassert>
6
+ #include <cstddef>
7
+ #include <cctype>
8
+ #include <cwctype>
9
+ #include <cstdint>
10
+ #include <condition_variable>
11
+ #include <mutex>
12
+ #include <thread>
13
+ #include <stdarg.h>
4
14
 
5
15
  #if defined(_WIN32)
6
16
  #define WIN32_LEAN_AND_MEAN
@@ -30,26 +40,44 @@
30
40
  #define ANSI_COLOR_BLUE "\x1b[34m"
31
41
  #define ANSI_COLOR_MAGENTA "\x1b[35m"
32
42
  #define ANSI_COLOR_CYAN "\x1b[36m"
43
+ #define ANSI_COLOR_GRAY "\x1b[90m"
33
44
  #define ANSI_COLOR_RESET "\x1b[0m"
34
45
  #define ANSI_BOLD "\x1b[1m"
35
46
 
36
47
  namespace console {
37
48
 
49
+ #if defined (_WIN32)
50
+ namespace {
51
+ // Use private-use unicode values to represent special keys that are not reported
52
+ // as characters (e.g. arrows on Windows). These values should never clash with
53
+ // real input and let the rest of the code handle navigation uniformly.
54
+ static constexpr char32_t KEY_ARROW_LEFT = 0xE000;
55
+ static constexpr char32_t KEY_ARROW_RIGHT = 0xE001;
56
+ static constexpr char32_t KEY_ARROW_UP = 0xE002;
57
+ static constexpr char32_t KEY_ARROW_DOWN = 0xE003;
58
+ static constexpr char32_t KEY_HOME = 0xE004;
59
+ static constexpr char32_t KEY_END = 0xE005;
60
+ static constexpr char32_t KEY_CTRL_ARROW_LEFT = 0xE006;
61
+ static constexpr char32_t KEY_CTRL_ARROW_RIGHT = 0xE007;
62
+ static constexpr char32_t KEY_DELETE = 0xE008;
63
+ }
64
+
38
65
  //
39
66
  // Console state
40
67
  //
68
+ #endif
41
69
 
42
- static bool advanced_display = false;
43
- static bool simple_io = true;
44
- static display_t current_display = reset;
70
+ static bool advanced_display = false;
71
+ static bool simple_io = true;
72
+ static display_type current_display = DISPLAY_TYPE_RESET;
45
73
 
46
- static FILE* out = stdout;
74
+ static FILE* out = stdout;
47
75
 
48
76
  #if defined (_WIN32)
49
- static void* hConsole;
77
+ static void* hConsole;
50
78
  #else
51
- static FILE* tty = nullptr;
52
- static termios initial_state;
79
+ static FILE* tty = nullptr;
80
+ static termios initial_state;
53
81
  #endif
54
82
 
55
83
  //
@@ -120,7 +148,7 @@ namespace console {
120
148
 
121
149
  void cleanup() {
122
150
  // Reset console display
123
- set_display(reset);
151
+ set_display(DISPLAY_TYPE_RESET);
124
152
 
125
153
  #if !defined(_WIN32)
126
154
  // Restore settings on POSIX systems
@@ -140,20 +168,26 @@ namespace console {
140
168
  //
141
169
 
142
170
  // Keep track of current display and only emit ANSI code if it changes
143
- void set_display(display_t display) {
171
+ void set_display(display_type display) {
144
172
  if (advanced_display && current_display != display) {
145
- fflush(stdout);
173
+ common_log_flush(common_log_main());
146
174
  switch(display) {
147
- case reset:
175
+ case DISPLAY_TYPE_RESET:
148
176
  fprintf(out, ANSI_COLOR_RESET);
149
177
  break;
150
- case prompt:
178
+ case DISPLAY_TYPE_INFO:
179
+ fprintf(out, ANSI_COLOR_MAGENTA);
180
+ break;
181
+ case DISPLAY_TYPE_PROMPT:
151
182
  fprintf(out, ANSI_COLOR_YELLOW);
152
183
  break;
153
- case user_input:
184
+ case DISPLAY_TYPE_REASONING:
185
+ fprintf(out, ANSI_COLOR_GRAY);
186
+ break;
187
+ case DISPLAY_TYPE_USER_INPUT:
154
188
  fprintf(out, ANSI_BOLD ANSI_COLOR_GREEN);
155
189
  break;
156
- case error:
190
+ case DISPLAY_TYPE_ERROR:
157
191
  fprintf(out, ANSI_BOLD ANSI_COLOR_RED);
158
192
  }
159
193
  current_display = display;
@@ -176,7 +210,18 @@ namespace console {
176
210
  if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown) {
177
211
  wchar_t wc = record.Event.KeyEvent.uChar.UnicodeChar;
178
212
  if (wc == 0) {
179
- continue;
213
+ const DWORD ctrl_mask = LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED;
214
+ const bool ctrl_pressed = (record.Event.KeyEvent.dwControlKeyState & ctrl_mask) != 0;
215
+ switch (record.Event.KeyEvent.wVirtualKeyCode) {
216
+ case VK_LEFT: return ctrl_pressed ? KEY_CTRL_ARROW_LEFT : KEY_ARROW_LEFT;
217
+ case VK_RIGHT: return ctrl_pressed ? KEY_CTRL_ARROW_RIGHT : KEY_ARROW_RIGHT;
218
+ case VK_UP: return KEY_ARROW_UP;
219
+ case VK_DOWN: return KEY_ARROW_DOWN;
220
+ case VK_HOME: return KEY_HOME;
221
+ case VK_END: return KEY_END;
222
+ case VK_DELETE: return KEY_DELETE;
223
+ default: continue;
224
+ }
180
225
  }
181
226
 
182
227
  if ((wc >= 0xD800) && (wc <= 0xDBFF)) { // Check if wc is a high surrogate
@@ -315,6 +360,52 @@ namespace console {
315
360
  #endif
316
361
  }
317
362
 
363
+ static char32_t decode_utf8(const std::string & input, size_t pos, size_t & advance) {
364
+ unsigned char c = static_cast<unsigned char>(input[pos]);
365
+ if ((c & 0x80u) == 0u) {
366
+ advance = 1;
367
+ return c;
368
+ }
369
+ if ((c & 0xE0u) == 0xC0u && pos + 1 < input.size()) {
370
+ unsigned char c1 = static_cast<unsigned char>(input[pos + 1]);
371
+ if ((c1 & 0xC0u) != 0x80u) {
372
+ advance = 1;
373
+ return 0xFFFD;
374
+ }
375
+ advance = 2;
376
+ return ((c & 0x1Fu) << 6) | (static_cast<unsigned char>(input[pos + 1]) & 0x3Fu);
377
+ }
378
+ if ((c & 0xF0u) == 0xE0u && pos + 2 < input.size()) {
379
+ unsigned char c1 = static_cast<unsigned char>(input[pos + 1]);
380
+ unsigned char c2 = static_cast<unsigned char>(input[pos + 2]);
381
+ if ((c1 & 0xC0u) != 0x80u || (c2 & 0xC0u) != 0x80u) {
382
+ advance = 1;
383
+ return 0xFFFD;
384
+ }
385
+ advance = 3;
386
+ return ((c & 0x0Fu) << 12) |
387
+ ((static_cast<unsigned char>(input[pos + 1]) & 0x3Fu) << 6) |
388
+ (static_cast<unsigned char>(input[pos + 2]) & 0x3Fu);
389
+ }
390
+ if ((c & 0xF8u) == 0xF0u && pos + 3 < input.size()) {
391
+ unsigned char c1 = static_cast<unsigned char>(input[pos + 1]);
392
+ unsigned char c2 = static_cast<unsigned char>(input[pos + 2]);
393
+ unsigned char c3 = static_cast<unsigned char>(input[pos + 3]);
394
+ if ((c1 & 0xC0u) != 0x80u || (c2 & 0xC0u) != 0x80u || (c3 & 0xC0u) != 0x80u) {
395
+ advance = 1;
396
+ return 0xFFFD;
397
+ }
398
+ advance = 4;
399
+ return ((c & 0x07u) << 18) |
400
+ ((static_cast<unsigned char>(input[pos + 1]) & 0x3Fu) << 12) |
401
+ ((static_cast<unsigned char>(input[pos + 2]) & 0x3Fu) << 6) |
402
+ (static_cast<unsigned char>(input[pos + 3]) & 0x3Fu);
403
+ }
404
+
405
+ advance = 1;
406
+ return 0xFFFD; // replacement character for invalid input
407
+ }
408
+
318
409
  static void append_utf8(char32_t ch, std::string & out) {
319
410
  if (ch <= 0x7F) {
320
411
  out.push_back(static_cast<unsigned char>(ch));
@@ -336,22 +427,319 @@ namespace console {
336
427
  }
337
428
 
338
429
  // Helper function to remove the last UTF-8 character from a string
339
- static void pop_back_utf8_char(std::string & line) {
340
- if (line.empty()) {
430
+ static size_t prev_utf8_char_pos(const std::string & line, size_t pos) {
431
+ if (pos == 0) return 0;
432
+ pos--;
433
+ while (pos > 0 && (line[pos] & 0xC0) == 0x80) {
434
+ pos--;
435
+ }
436
+ return pos;
437
+ }
438
+
439
+ static size_t next_utf8_char_pos(const std::string & line, size_t pos) {
440
+ if (pos >= line.length()) return line.length();
441
+ pos++;
442
+ while (pos < line.length() && (line[pos] & 0xC0) == 0x80) {
443
+ pos++;
444
+ }
445
+ return pos;
446
+ }
447
+
448
+ static void move_cursor(int delta);
449
+ static void move_word_left(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths, const std::string & line);
450
+ static void move_word_right(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths, const std::string & line);
451
+ static void move_to_line_start(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths);
452
+ static void move_to_line_end(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths, const std::string & line);
453
+
454
+ static void delete_at_cursor(std::string & line, std::vector<int> & widths, size_t & char_pos, size_t & byte_pos) {
455
+ if (char_pos >= widths.size()) {
456
+ return;
457
+ }
458
+
459
+ size_t next_pos = next_utf8_char_pos(line, byte_pos);
460
+ int w = widths[char_pos];
461
+ size_t char_len = next_pos - byte_pos;
462
+
463
+ line.erase(byte_pos, char_len);
464
+ widths.erase(widths.begin() + char_pos);
465
+
466
+ size_t p = byte_pos;
467
+ int tail_width = 0;
468
+ for (size_t i = char_pos; i < widths.size(); ++i) {
469
+ size_t following = next_utf8_char_pos(line, p);
470
+ put_codepoint(line.c_str() + p, following - p, widths[i]);
471
+ tail_width += widths[i];
472
+ p = following;
473
+ }
474
+
475
+ for (int i = 0; i < w; ++i) {
476
+ fputc(' ', out);
477
+ }
478
+
479
+ move_cursor(-(tail_width + w));
480
+ }
481
+
482
+ static void clear_current_line(const std::vector<int> & widths) {
483
+ int total_width = 0;
484
+ for (int w : widths) {
485
+ total_width += (w > 0 ? w : 1);
486
+ }
487
+
488
+ if (total_width > 0) {
489
+ std::string spaces(total_width, ' ');
490
+ fwrite(spaces.c_str(), 1, total_width, out);
491
+ move_cursor(-total_width);
492
+ }
493
+ }
494
+
495
+ static void set_line_contents(std::string new_line, std::string & line, std::vector<int> & widths, size_t & char_pos,
496
+ size_t & byte_pos) {
497
+ move_to_line_start(char_pos, byte_pos, widths);
498
+ clear_current_line(widths);
499
+
500
+ line = std::move(new_line);
501
+ widths.clear();
502
+ byte_pos = 0;
503
+ char_pos = 0;
504
+
505
+ size_t idx = 0;
506
+ while (idx < line.size()) {
507
+ size_t advance = 0;
508
+ char32_t cp = decode_utf8(line, idx, advance);
509
+ int expected_width = estimateWidth(cp);
510
+ int real_width = put_codepoint(line.c_str() + idx, advance, expected_width);
511
+ if (real_width < 0) real_width = 0;
512
+ widths.push_back(real_width);
513
+ idx += advance;
514
+ ++char_pos;
515
+ byte_pos = idx;
516
+ }
517
+ }
518
+
519
+ static void move_to_line_start(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths) {
520
+ int back_width = 0;
521
+ for (size_t i = 0; i < char_pos; ++i) {
522
+ back_width += widths[i];
523
+ }
524
+ move_cursor(-back_width);
525
+ char_pos = 0;
526
+ byte_pos = 0;
527
+ }
528
+
529
+ static void move_to_line_end(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths, const std::string & line) {
530
+ int forward_width = 0;
531
+ for (size_t i = char_pos; i < widths.size(); ++i) {
532
+ forward_width += widths[i];
533
+ }
534
+ move_cursor(forward_width);
535
+ char_pos = widths.size();
536
+ byte_pos = line.length();
537
+ }
538
+
539
+ static bool has_ctrl_modifier(const std::string & params) {
540
+ size_t start = 0;
541
+ while (start < params.size()) {
542
+ size_t end = params.find(';', start);
543
+ size_t len = (end == std::string::npos) ? params.size() - start : end - start;
544
+ if (len > 0) {
545
+ int value = 0;
546
+ for (size_t i = 0; i < len; ++i) {
547
+ char ch = params[start + i];
548
+ if (!std::isdigit(static_cast<unsigned char>(ch))) {
549
+ value = -1;
550
+ break;
551
+ }
552
+ value = value * 10 + (ch - '0');
553
+ }
554
+ if (value == 5) {
555
+ return true;
556
+ }
557
+ }
558
+
559
+ if (end == std::string::npos) {
560
+ break;
561
+ }
562
+ start = end + 1;
563
+ }
564
+ return false;
565
+ }
566
+
567
+ static bool is_space_codepoint(char32_t cp) {
568
+ return std::iswspace(static_cast<wint_t>(cp)) != 0;
569
+ }
570
+
571
+ static void move_word_left(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths, const std::string & line) {
572
+ if (char_pos == 0) {
341
573
  return;
342
574
  }
343
575
 
344
- size_t pos = line.length() - 1;
576
+ size_t new_char_pos = char_pos;
577
+ size_t new_byte_pos = byte_pos;
578
+ int move_width = 0;
345
579
 
346
- // Find the start of the last UTF-8 character (checking up to 4 bytes back)
347
- for (size_t i = 0; i < 3 && pos > 0; ++i, --pos) {
348
- if ((line[pos] & 0xC0) != 0x80) {
349
- break; // Found the start of the character
580
+ while (new_char_pos > 0) {
581
+ size_t prev_byte = prev_utf8_char_pos(line, new_byte_pos);
582
+ size_t advance = 0;
583
+ char32_t cp = decode_utf8(line, prev_byte, advance);
584
+ if (!is_space_codepoint(cp)) {
585
+ break;
586
+ }
587
+ move_width += widths[new_char_pos - 1];
588
+ new_char_pos--;
589
+ new_byte_pos = prev_byte;
590
+ }
591
+
592
+ while (new_char_pos > 0) {
593
+ size_t prev_byte = prev_utf8_char_pos(line, new_byte_pos);
594
+ size_t advance = 0;
595
+ char32_t cp = decode_utf8(line, prev_byte, advance);
596
+ if (is_space_codepoint(cp)) {
597
+ break;
350
598
  }
599
+ move_width += widths[new_char_pos - 1];
600
+ new_char_pos--;
601
+ new_byte_pos = prev_byte;
351
602
  }
352
- line.erase(pos);
603
+
604
+ move_cursor(-move_width);
605
+ char_pos = new_char_pos;
606
+ byte_pos = new_byte_pos;
353
607
  }
354
608
 
609
+ static void move_word_right(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths, const std::string & line) {
610
+ if (char_pos >= widths.size()) {
611
+ return;
612
+ }
613
+
614
+ size_t new_char_pos = char_pos;
615
+ size_t new_byte_pos = byte_pos;
616
+ int move_width = 0;
617
+
618
+ while (new_char_pos < widths.size()) {
619
+ size_t advance = 0;
620
+ char32_t cp = decode_utf8(line, new_byte_pos, advance);
621
+ if (!is_space_codepoint(cp)) {
622
+ break;
623
+ }
624
+ move_width += widths[new_char_pos];
625
+ new_char_pos++;
626
+ new_byte_pos += advance;
627
+ }
628
+
629
+ while (new_char_pos < widths.size()) {
630
+ size_t advance = 0;
631
+ char32_t cp = decode_utf8(line, new_byte_pos, advance);
632
+ if (is_space_codepoint(cp)) {
633
+ break;
634
+ }
635
+ move_width += widths[new_char_pos];
636
+ new_char_pos++;
637
+ new_byte_pos += advance;
638
+ }
639
+
640
+ while (new_char_pos < widths.size()) {
641
+ size_t advance = 0;
642
+ char32_t cp = decode_utf8(line, new_byte_pos, advance);
643
+ if (!is_space_codepoint(cp)) {
644
+ break;
645
+ }
646
+ move_width += widths[new_char_pos];
647
+ new_char_pos++;
648
+ new_byte_pos += advance;
649
+ }
650
+
651
+ move_cursor(move_width);
652
+ char_pos = new_char_pos;
653
+ byte_pos = new_byte_pos;
654
+ }
655
+
656
+ static void move_cursor(int delta) {
657
+ if (delta == 0) return;
658
+ #if defined(_WIN32)
659
+ if (hConsole != NULL) {
660
+ CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
661
+ GetConsoleScreenBufferInfo(hConsole, &bufferInfo);
662
+ COORD newCursorPosition = bufferInfo.dwCursorPosition;
663
+ int width = bufferInfo.dwSize.X;
664
+ int newX = newCursorPosition.X + delta;
665
+ int newY = newCursorPosition.Y;
666
+
667
+ while (newX >= width) {
668
+ newX -= width;
669
+ newY++;
670
+ }
671
+ while (newX < 0) {
672
+ newX += width;
673
+ newY--;
674
+ }
675
+
676
+ newCursorPosition.X = newX;
677
+ newCursorPosition.Y = newY;
678
+ SetConsoleCursorPosition(hConsole, newCursorPosition);
679
+ }
680
+ #else
681
+ if (delta < 0) {
682
+ for (int i = 0; i < -delta; i++) fprintf(out, "\b");
683
+ } else {
684
+ for (int i = 0; i < delta; i++) fprintf(out, "\033[C");
685
+ }
686
+ #endif
687
+ }
688
+
689
+ struct history_t {
690
+ std::vector<std::string> entries;
691
+ size_t viewing_idx = SIZE_MAX;
692
+ std::string backup_line; // current line before viewing history
693
+ void add(const std::string & line) {
694
+ if (line.empty()) {
695
+ return;
696
+ }
697
+ // avoid duplicates with the last entry
698
+ if (entries.empty() || entries.back() != line) {
699
+ entries.push_back(line);
700
+ }
701
+ // also clear viewing state
702
+ end_viewing();
703
+ }
704
+ bool prev(std::string & cur_line) {
705
+ if (entries.empty()) {
706
+ return false;
707
+ }
708
+ if (viewing_idx == SIZE_MAX) {
709
+ return false;
710
+ }
711
+ if (viewing_idx > 0) {
712
+ viewing_idx--;
713
+ }
714
+ cur_line = entries[viewing_idx];
715
+ return true;
716
+ }
717
+ bool next(std::string & cur_line) {
718
+ if (entries.empty() || viewing_idx == SIZE_MAX) {
719
+ return false;
720
+ }
721
+ viewing_idx++;
722
+ if (viewing_idx >= entries.size()) {
723
+ cur_line = backup_line;
724
+ end_viewing();
725
+ } else {
726
+ cur_line = entries[viewing_idx];
727
+ }
728
+ return true;
729
+ }
730
+ void begin_viewing(const std::string & line) {
731
+ backup_line = line;
732
+ viewing_idx = entries.size();
733
+ }
734
+ void end_viewing() {
735
+ viewing_idx = SIZE_MAX;
736
+ backup_line.clear();
737
+ }
738
+ bool is_viewing() const {
739
+ return viewing_idx != SIZE_MAX;
740
+ }
741
+ } history;
742
+
355
743
  static bool readline_advanced(std::string & line, bool multiline_input) {
356
744
  if (out != stdout) {
357
745
  fflush(stdout);
@@ -362,8 +750,33 @@ namespace console {
362
750
  bool is_special_char = false;
363
751
  bool end_of_stream = false;
364
752
 
753
+ size_t byte_pos = 0; // current byte index
754
+ size_t char_pos = 0; // current character index (one char can be multiple bytes)
755
+
365
756
  char32_t input_char;
366
757
  while (true) {
758
+ assert(char_pos <= byte_pos);
759
+ assert(char_pos <= widths.size());
760
+ auto history_prev = [&]() {
761
+ if (!history.is_viewing()) {
762
+ history.begin_viewing(line);
763
+ }
764
+ std::string new_line;
765
+ if (!history.prev(new_line)) {
766
+ return;
767
+ }
768
+ set_line_contents(new_line, line, widths, char_pos, byte_pos);
769
+ };
770
+ auto history_next = [&]() {
771
+ if (history.is_viewing()) {
772
+ std::string new_line;
773
+ if (!history.next(new_line)) {
774
+ return;
775
+ }
776
+ set_line_contents(new_line, line, widths, char_pos, byte_pos);
777
+ }
778
+ };
779
+
367
780
  fflush(out); // Ensure all output is displayed before waiting for input
368
781
  input_char = getchar32();
369
782
 
@@ -371,20 +784,83 @@ namespace console {
371
784
  break;
372
785
  }
373
786
 
374
- if (input_char == (char32_t) WEOF || input_char == 0x04 /* Ctrl+D*/) {
787
+ if (input_char == (char32_t) WEOF || input_char == 0x04 /* Ctrl+D */) {
375
788
  end_of_stream = true;
376
789
  break;
377
790
  }
378
791
 
379
792
  if (is_special_char) {
380
- set_display(user_input);
381
793
  replace_last(line.back());
382
794
  is_special_char = false;
383
795
  }
384
796
 
385
797
  if (input_char == '\033') { // Escape sequence
386
798
  char32_t code = getchar32();
387
- if (code == '[' || code == 0x1B) {
799
+ if (code == '[') {
800
+ std::string params;
801
+ while (true) {
802
+ code = getchar32();
803
+ if ((code >= 'A' && code <= 'Z') || (code >= 'a' && code <= 'z') || code == '~' || code == (char32_t) WEOF) {
804
+ break;
805
+ }
806
+ params.push_back(static_cast<char>(code));
807
+ }
808
+
809
+ const bool ctrl_modifier = has_ctrl_modifier(params);
810
+
811
+ if (code == 'D') { // left
812
+ if (ctrl_modifier) {
813
+ move_word_left(char_pos, byte_pos, widths, line);
814
+ } else if (char_pos > 0) {
815
+ int w = widths[char_pos - 1];
816
+ move_cursor(-w);
817
+ char_pos--;
818
+ byte_pos = prev_utf8_char_pos(line, byte_pos);
819
+ }
820
+ } else if (code == 'C') { // right
821
+ if (ctrl_modifier) {
822
+ move_word_right(char_pos, byte_pos, widths, line);
823
+ } else if (char_pos < widths.size()) {
824
+ int w = widths[char_pos];
825
+ move_cursor(w);
826
+ char_pos++;
827
+ byte_pos = next_utf8_char_pos(line, byte_pos);
828
+ }
829
+ } else if (code == 'H') { // home
830
+ move_to_line_start(char_pos, byte_pos, widths);
831
+ } else if (code == 'F') { // end
832
+ move_to_line_end(char_pos, byte_pos, widths, line);
833
+ } else if (code == 'A' || code == 'B') {
834
+ // up/down
835
+ if (code == 'A') {
836
+ history_prev();
837
+ is_special_char = false;
838
+ } else if (code == 'B') {
839
+ history_next();
840
+ is_special_char = false;
841
+ }
842
+ } else if ((code == '~' || (code >= 'A' && code <= 'Z') || (code >= 'a' && code <= 'z')) && !params.empty()) {
843
+ std::string digits;
844
+ for (char ch : params) {
845
+ if (ch == ';') {
846
+ break;
847
+ }
848
+ if (std::isdigit(static_cast<unsigned char>(ch))) {
849
+ digits.push_back(ch);
850
+ }
851
+ }
852
+
853
+ if (code == '~') {
854
+ if (digits == "1" || digits == "7") { // home
855
+ move_to_line_start(char_pos, byte_pos, widths);
856
+ } else if (digits == "4" || digits == "8") { // end
857
+ move_to_line_end(char_pos, byte_pos, widths, line);
858
+ } else if (digits == "3") { // delete
859
+ delete_at_cursor(line, widths, char_pos, byte_pos);
860
+ }
861
+ }
862
+ }
863
+ } else if (code == 0x1B) {
388
864
  // Discard the rest of the escape sequence
389
865
  while ((code = getchar32()) != (char32_t) WEOF) {
390
866
  if ((code >= 'A' && code <= 'Z') || (code >= 'a' && code <= 'z') || code == '~') {
@@ -392,32 +868,110 @@ namespace console {
392
868
  }
393
869
  }
394
870
  }
871
+ #if defined(_WIN32)
872
+ } else if (input_char == KEY_ARROW_LEFT) {
873
+ if (char_pos > 0) {
874
+ int w = widths[char_pos - 1];
875
+ move_cursor(-w);
876
+ char_pos--;
877
+ byte_pos = prev_utf8_char_pos(line, byte_pos);
878
+ }
879
+ } else if (input_char == KEY_ARROW_RIGHT) {
880
+ if (char_pos < widths.size()) {
881
+ int w = widths[char_pos];
882
+ move_cursor(w);
883
+ char_pos++;
884
+ byte_pos = next_utf8_char_pos(line, byte_pos);
885
+ }
886
+ } else if (input_char == KEY_CTRL_ARROW_LEFT) {
887
+ move_word_left(char_pos, byte_pos, widths, line);
888
+ } else if (input_char == KEY_CTRL_ARROW_RIGHT) {
889
+ move_word_right(char_pos, byte_pos, widths, line);
890
+ } else if (input_char == KEY_HOME) {
891
+ move_to_line_start(char_pos, byte_pos, widths);
892
+ } else if (input_char == KEY_END) {
893
+ move_to_line_end(char_pos, byte_pos, widths, line);
894
+ } else if (input_char == KEY_DELETE) {
895
+ delete_at_cursor(line, widths, char_pos, byte_pos);
896
+ } else if (input_char == KEY_ARROW_UP || input_char == KEY_ARROW_DOWN) {
897
+ if (input_char == KEY_ARROW_UP) {
898
+ history_prev();
899
+ is_special_char = false;
900
+ } else if (input_char == KEY_ARROW_DOWN) {
901
+ history_next();
902
+ is_special_char = false;
903
+ }
904
+ #endif
395
905
  } else if (input_char == 0x08 || input_char == 0x7F) { // Backspace
396
- if (!widths.empty()) {
397
- int count;
398
- do {
399
- count = widths.back();
400
- widths.pop_back();
401
- // Move cursor back, print space, and move cursor back again
402
- for (int i = 0; i < count; i++) {
403
- replace_last(' ');
404
- pop_cursor();
405
- }
406
- pop_back_utf8_char(line);
407
- } while (count == 0 && !widths.empty());
906
+ if (char_pos > 0) {
907
+ int w = widths[char_pos - 1];
908
+ move_cursor(-w);
909
+ char_pos--;
910
+ size_t prev_pos = prev_utf8_char_pos(line, byte_pos);
911
+ size_t char_len = byte_pos - prev_pos;
912
+ byte_pos = prev_pos;
913
+
914
+ // remove the character
915
+ line.erase(byte_pos, char_len);
916
+ widths.erase(widths.begin() + char_pos);
917
+
918
+ // redraw tail
919
+ size_t p = byte_pos;
920
+ int tail_width = 0;
921
+ for (size_t i = char_pos; i < widths.size(); ++i) {
922
+ size_t next_p = next_utf8_char_pos(line, p);
923
+ put_codepoint(line.c_str() + p, next_p - p, widths[i]);
924
+ tail_width += widths[i];
925
+ p = next_p;
926
+ }
927
+
928
+ // clear display
929
+ for (int i = 0; i < w; ++i) {
930
+ fputc(' ', out);
931
+ }
932
+ move_cursor(-(tail_width + w));
408
933
  }
409
934
  } else {
410
- int offset = line.length();
411
- append_utf8(input_char, line);
412
- int width = put_codepoint(line.c_str() + offset, line.length() - offset, estimateWidth(input_char));
413
- if (width < 0) {
414
- width = 0;
935
+ // insert character
936
+ std::string new_char_str;
937
+ append_utf8(input_char, new_char_str);
938
+ int w = estimateWidth(input_char);
939
+
940
+ if (char_pos == widths.size()) {
941
+ // insert at the end
942
+ line += new_char_str;
943
+ int real_w = put_codepoint(new_char_str.c_str(), new_char_str.length(), w);
944
+ if (real_w < 0) real_w = 0;
945
+ widths.push_back(real_w);
946
+ byte_pos += new_char_str.length();
947
+ char_pos++;
948
+ } else {
949
+ // insert in middle
950
+ line.insert(byte_pos, new_char_str);
951
+
952
+ int real_w = put_codepoint(new_char_str.c_str(), new_char_str.length(), w);
953
+ if (real_w < 0) real_w = 0;
954
+
955
+ widths.insert(widths.begin() + char_pos, real_w);
956
+
957
+ // print the tail
958
+ size_t p = byte_pos + new_char_str.length();
959
+ int tail_width = 0;
960
+ for (size_t i = char_pos + 1; i < widths.size(); ++i) {
961
+ size_t next_p = next_utf8_char_pos(line, p);
962
+ put_codepoint(line.c_str() + p, next_p - p, widths[i]);
963
+ tail_width += widths[i];
964
+ p = next_p;
965
+ }
966
+
967
+ move_cursor(-tail_width);
968
+
969
+ byte_pos += new_char_str.length();
970
+ char_pos++;
415
971
  }
416
- widths.push_back(width);
417
972
  }
418
973
 
419
974
  if (!line.empty() && (line.back() == '\\' || line.back() == '/')) {
420
- set_display(prompt);
421
975
  replace_last(line.back());
422
976
  is_special_char = true;
423
977
  }
@@ -451,6 +1005,15 @@ namespace console {
451
1005
  }
452
1006
  }
453
1007
 
1008
+ if (!end_of_stream && !line.empty()) {
1009
+ // remove the trailing newline for history storage
1010
+ if (!line.empty() && line.back() == '\n') {
1011
+ line.pop_back();
1012
+ }
1013
+ // TODO: maybe support multiline history entries?
1014
+ history.add(line);
1015
+ }
1016
+
454
1017
  fflush(out);
455
1018
  return has_more;
456
1019
  }
@@ -493,12 +1056,82 @@ namespace console {
493
1056
  }
494
1057
 
495
1058
  bool readline(std::string & line, bool multiline_input) {
496
- set_display(user_input);
497
-
498
1059
  if (simple_io) {
499
1060
  return readline_simple(line, multiline_input);
500
1061
  }
501
1062
  return readline_advanced(line, multiline_input);
502
1063
  }
503
1064
 
1065
+ namespace spinner {
1066
+ static const char LOADING_CHARS[] = {'|', '/', '-', '\\'};
1067
+ static std::condition_variable cv_stop;
1068
+ static std::thread th;
1069
+ static size_t frame = 0; // only modified by one thread
1070
+ static bool running = false;
1071
+ static std::mutex mtx;
1072
+ static auto wait_time = std::chrono::milliseconds(100);
1073
+ static void draw_next_frame() {
1074
+ // don't need lock because only one thread modifies running
1075
+ frame = (frame + 1) % sizeof(LOADING_CHARS);
1076
+ replace_last(LOADING_CHARS[frame]);
1077
+ fflush(out);
1078
+ }
1079
+ void start() {
1080
+ std::unique_lock<std::mutex> lock(mtx);
1081
+ if (simple_io || running) {
1082
+ return;
1083
+ }
1084
+ common_log_flush(common_log_main());
1085
+ fprintf(out, "%c", LOADING_CHARS[0]);
1086
+ fflush(out);
1087
+ frame = 1;
1088
+ running = true;
1089
+ th = std::thread([]() {
1090
+ std::unique_lock<std::mutex> lock(mtx);
1091
+ while (true) {
1092
+ if (cv_stop.wait_for(lock, wait_time, []{ return !running; })) {
1093
+ break;
1094
+ }
1095
+ draw_next_frame();
1096
+ }
1097
+ });
1098
+ }
1099
+ void stop() {
1100
+ {
1101
+ std::unique_lock<std::mutex> lock(mtx);
1102
+ if (simple_io || !running) {
1103
+ return;
1104
+ }
1105
+ running = false;
1106
+ cv_stop.notify_all();
1107
+ }
1108
+ if (th.joinable()) {
1109
+ th.join();
1110
+ }
1111
+ replace_last(' ');
1112
+ pop_cursor();
1113
+ fflush(out);
1114
+ }
1115
+ }
1116
+
1117
+ void log(const char * fmt, ...) {
1118
+ va_list args;
1119
+ va_start(args, fmt);
1120
+ vfprintf(out, fmt, args);
1121
+ va_end(args);
1122
+ }
1123
+
1124
+ void error(const char * fmt, ...) {
1125
+ va_list args;
1126
+ va_start(args, fmt);
1127
+ display_type cur = current_display;
1128
+ set_display(DISPLAY_TYPE_ERROR);
1129
+ vfprintf(out, fmt, args);
1130
+ set_display(cur); // restore previous color
1131
+ va_end(args);
1132
+ }
1133
+
1134
+ void flush() {
1135
+ fflush(out);
1136
+ }
504
1137
  }