@fugood/llama.node 1.4.6 → 1.4.7

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,6 +1,11 @@
1
1
  #include "console.h"
2
2
  #include <vector>
3
3
  #include <iostream>
4
+ #include <cassert>
5
+ #include <cstddef>
6
+ #include <cctype>
7
+ #include <cwctype>
8
+ #include <cstdint>
4
9
 
5
10
  #if defined(_WIN32)
6
11
  #define WIN32_LEAN_AND_MEAN
@@ -35,9 +40,26 @@
35
40
 
36
41
  namespace console {
37
42
 
43
+ #if defined (_WIN32)
44
+ namespace {
45
+ // Use private-use unicode values to represent special keys that are not reported
46
+ // as characters (e.g. arrows on Windows). These values should never clash with
47
+ // real input and let the rest of the code handle navigation uniformly.
48
+ static constexpr char32_t KEY_ARROW_LEFT = 0xE000;
49
+ static constexpr char32_t KEY_ARROW_RIGHT = 0xE001;
50
+ static constexpr char32_t KEY_ARROW_UP = 0xE002;
51
+ static constexpr char32_t KEY_ARROW_DOWN = 0xE003;
52
+ static constexpr char32_t KEY_HOME = 0xE004;
53
+ static constexpr char32_t KEY_END = 0xE005;
54
+ static constexpr char32_t KEY_CTRL_ARROW_LEFT = 0xE006;
55
+ static constexpr char32_t KEY_CTRL_ARROW_RIGHT = 0xE007;
56
+ static constexpr char32_t KEY_DELETE = 0xE008;
57
+ }
58
+
38
59
  //
39
60
  // Console state
40
61
  //
62
+ #endif
41
63
 
42
64
  static bool advanced_display = false;
43
65
  static bool simple_io = true;
@@ -176,7 +198,18 @@ namespace console {
176
198
  if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown) {
177
199
  wchar_t wc = record.Event.KeyEvent.uChar.UnicodeChar;
178
200
  if (wc == 0) {
179
- continue;
201
+ const DWORD ctrl_mask = LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED;
202
+ const bool ctrl_pressed = (record.Event.KeyEvent.dwControlKeyState & ctrl_mask) != 0;
203
+ switch (record.Event.KeyEvent.wVirtualKeyCode) {
204
+ case VK_LEFT: return ctrl_pressed ? KEY_CTRL_ARROW_LEFT : KEY_ARROW_LEFT;
205
+ case VK_RIGHT: return ctrl_pressed ? KEY_CTRL_ARROW_RIGHT : KEY_ARROW_RIGHT;
206
+ case VK_UP: return KEY_ARROW_UP;
207
+ case VK_DOWN: return KEY_ARROW_DOWN;
208
+ case VK_HOME: return KEY_HOME;
209
+ case VK_END: return KEY_END;
210
+ case VK_DELETE: return KEY_DELETE;
211
+ default: continue;
212
+ }
180
213
  }
181
214
 
182
215
  if ((wc >= 0xD800) && (wc <= 0xDBFF)) { // Check if wc is a high surrogate
@@ -315,6 +348,52 @@ namespace console {
315
348
  #endif
316
349
  }
317
350
 
351
+ static char32_t decode_utf8(const std::string & input, size_t pos, size_t & advance) {
352
+ unsigned char c = static_cast<unsigned char>(input[pos]);
353
+ if ((c & 0x80u) == 0u) {
354
+ advance = 1;
355
+ return c;
356
+ }
357
+ if ((c & 0xE0u) == 0xC0u && pos + 1 < input.size()) {
358
+ unsigned char c1 = static_cast<unsigned char>(input[pos + 1]);
359
+ if ((c1 & 0xC0u) != 0x80u) {
360
+ advance = 1;
361
+ return 0xFFFD;
362
+ }
363
+ advance = 2;
364
+ return ((c & 0x1Fu) << 6) | (static_cast<unsigned char>(input[pos + 1]) & 0x3Fu);
365
+ }
366
+ if ((c & 0xF0u) == 0xE0u && pos + 2 < input.size()) {
367
+ unsigned char c1 = static_cast<unsigned char>(input[pos + 1]);
368
+ unsigned char c2 = static_cast<unsigned char>(input[pos + 2]);
369
+ if ((c1 & 0xC0u) != 0x80u || (c2 & 0xC0u) != 0x80u) {
370
+ advance = 1;
371
+ return 0xFFFD;
372
+ }
373
+ advance = 3;
374
+ return ((c & 0x0Fu) << 12) |
375
+ ((static_cast<unsigned char>(input[pos + 1]) & 0x3Fu) << 6) |
376
+ (static_cast<unsigned char>(input[pos + 2]) & 0x3Fu);
377
+ }
378
+ if ((c & 0xF8u) == 0xF0u && pos + 3 < 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
+ unsigned char c3 = static_cast<unsigned char>(input[pos + 3]);
382
+ if ((c1 & 0xC0u) != 0x80u || (c2 & 0xC0u) != 0x80u || (c3 & 0xC0u) != 0x80u) {
383
+ advance = 1;
384
+ return 0xFFFD;
385
+ }
386
+ advance = 4;
387
+ return ((c & 0x07u) << 18) |
388
+ ((static_cast<unsigned char>(input[pos + 1]) & 0x3Fu) << 12) |
389
+ ((static_cast<unsigned char>(input[pos + 2]) & 0x3Fu) << 6) |
390
+ (static_cast<unsigned char>(input[pos + 3]) & 0x3Fu);
391
+ }
392
+
393
+ advance = 1;
394
+ return 0xFFFD; // replacement character for invalid input
395
+ }
396
+
318
397
  static void append_utf8(char32_t ch, std::string & out) {
319
398
  if (ch <= 0x7F) {
320
399
  out.push_back(static_cast<unsigned char>(ch));
@@ -336,22 +415,319 @@ namespace console {
336
415
  }
337
416
 
338
417
  // 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()) {
418
+ static size_t prev_utf8_char_pos(const std::string & line, size_t pos) {
419
+ if (pos == 0) return 0;
420
+ pos--;
421
+ while (pos > 0 && (line[pos] & 0xC0) == 0x80) {
422
+ pos--;
423
+ }
424
+ return pos;
425
+ }
426
+
427
+ static size_t next_utf8_char_pos(const std::string & line, size_t pos) {
428
+ if (pos >= line.length()) return line.length();
429
+ pos++;
430
+ while (pos < line.length() && (line[pos] & 0xC0) == 0x80) {
431
+ pos++;
432
+ }
433
+ return pos;
434
+ }
435
+
436
+ static void move_cursor(int delta);
437
+ static void move_word_left(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths, const std::string & line);
438
+ static void move_word_right(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths, const std::string & line);
439
+ static void move_to_line_start(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths);
440
+ static void move_to_line_end(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths, const std::string & line);
441
+
442
+ static void delete_at_cursor(std::string & line, std::vector<int> & widths, size_t & char_pos, size_t & byte_pos) {
443
+ if (char_pos >= widths.size()) {
341
444
  return;
342
445
  }
343
446
 
344
- size_t pos = line.length() - 1;
447
+ size_t next_pos = next_utf8_char_pos(line, byte_pos);
448
+ int w = widths[char_pos];
449
+ size_t char_len = next_pos - byte_pos;
450
+
451
+ line.erase(byte_pos, char_len);
452
+ widths.erase(widths.begin() + char_pos);
453
+
454
+ size_t p = byte_pos;
455
+ int tail_width = 0;
456
+ for (size_t i = char_pos; i < widths.size(); ++i) {
457
+ size_t following = next_utf8_char_pos(line, p);
458
+ put_codepoint(line.c_str() + p, following - p, widths[i]);
459
+ tail_width += widths[i];
460
+ p = following;
461
+ }
462
+
463
+ for (int i = 0; i < w; ++i) {
464
+ fputc(' ', out);
465
+ }
466
+
467
+ move_cursor(-(tail_width + w));
468
+ }
469
+
470
+ static void clear_current_line(const std::vector<int> & widths) {
471
+ int total_width = 0;
472
+ for (int w : widths) {
473
+ total_width += (w > 0 ? w : 1);
474
+ }
475
+
476
+ if (total_width > 0) {
477
+ std::string spaces(total_width, ' ');
478
+ fwrite(spaces.c_str(), 1, total_width, out);
479
+ move_cursor(-total_width);
480
+ }
481
+ }
482
+
483
+ static void set_line_contents(std::string new_line, std::string & line, std::vector<int> & widths, size_t & char_pos,
484
+ size_t & byte_pos) {
485
+ move_to_line_start(char_pos, byte_pos, widths);
486
+ clear_current_line(widths);
345
487
 
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
488
+ line = std::move(new_line);
489
+ widths.clear();
490
+ byte_pos = 0;
491
+ char_pos = 0;
492
+
493
+ size_t idx = 0;
494
+ while (idx < line.size()) {
495
+ size_t advance = 0;
496
+ char32_t cp = decode_utf8(line, idx, advance);
497
+ int expected_width = estimateWidth(cp);
498
+ int real_width = put_codepoint(line.c_str() + idx, advance, expected_width);
499
+ if (real_width < 0) real_width = 0;
500
+ widths.push_back(real_width);
501
+ idx += advance;
502
+ ++char_pos;
503
+ byte_pos = idx;
504
+ }
505
+ }
506
+
507
+ static void move_to_line_start(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths) {
508
+ int back_width = 0;
509
+ for (size_t i = 0; i < char_pos; ++i) {
510
+ back_width += widths[i];
511
+ }
512
+ move_cursor(-back_width);
513
+ char_pos = 0;
514
+ byte_pos = 0;
515
+ }
516
+
517
+ static void move_to_line_end(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths, const std::string & line) {
518
+ int forward_width = 0;
519
+ for (size_t i = char_pos; i < widths.size(); ++i) {
520
+ forward_width += widths[i];
521
+ }
522
+ move_cursor(forward_width);
523
+ char_pos = widths.size();
524
+ byte_pos = line.length();
525
+ }
526
+
527
+ static bool has_ctrl_modifier(const std::string & params) {
528
+ size_t start = 0;
529
+ while (start < params.size()) {
530
+ size_t end = params.find(';', start);
531
+ size_t len = (end == std::string::npos) ? params.size() - start : end - start;
532
+ if (len > 0) {
533
+ int value = 0;
534
+ for (size_t i = 0; i < len; ++i) {
535
+ char ch = params[start + i];
536
+ if (!std::isdigit(static_cast<unsigned char>(ch))) {
537
+ value = -1;
538
+ break;
539
+ }
540
+ value = value * 10 + (ch - '0');
541
+ }
542
+ if (value == 5) {
543
+ return true;
544
+ }
545
+ }
546
+
547
+ if (end == std::string::npos) {
548
+ break;
549
+ }
550
+ start = end + 1;
551
+ }
552
+ return false;
553
+ }
554
+
555
+ static bool is_space_codepoint(char32_t cp) {
556
+ return std::iswspace(static_cast<wint_t>(cp)) != 0;
557
+ }
558
+
559
+ static void move_word_left(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths, const std::string & line) {
560
+ if (char_pos == 0) {
561
+ return;
562
+ }
563
+
564
+ size_t new_char_pos = char_pos;
565
+ size_t new_byte_pos = byte_pos;
566
+ int move_width = 0;
567
+
568
+ while (new_char_pos > 0) {
569
+ size_t prev_byte = prev_utf8_char_pos(line, new_byte_pos);
570
+ size_t advance = 0;
571
+ char32_t cp = decode_utf8(line, prev_byte, advance);
572
+ if (!is_space_codepoint(cp)) {
573
+ break;
574
+ }
575
+ move_width += widths[new_char_pos - 1];
576
+ new_char_pos--;
577
+ new_byte_pos = prev_byte;
578
+ }
579
+
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;
350
586
  }
587
+ move_width += widths[new_char_pos - 1];
588
+ new_char_pos--;
589
+ new_byte_pos = prev_byte;
351
590
  }
352
- line.erase(pos);
591
+
592
+ move_cursor(-move_width);
593
+ char_pos = new_char_pos;
594
+ byte_pos = new_byte_pos;
353
595
  }
354
596
 
597
+ static void move_word_right(size_t & char_pos, size_t & byte_pos, const std::vector<int> & widths, const std::string & line) {
598
+ if (char_pos >= widths.size()) {
599
+ return;
600
+ }
601
+
602
+ size_t new_char_pos = char_pos;
603
+ size_t new_byte_pos = byte_pos;
604
+ int move_width = 0;
605
+
606
+ while (new_char_pos < widths.size()) {
607
+ size_t advance = 0;
608
+ char32_t cp = decode_utf8(line, new_byte_pos, advance);
609
+ if (!is_space_codepoint(cp)) {
610
+ break;
611
+ }
612
+ move_width += widths[new_char_pos];
613
+ new_char_pos++;
614
+ new_byte_pos += advance;
615
+ }
616
+
617
+ while (new_char_pos < widths.size()) {
618
+ size_t advance = 0;
619
+ char32_t cp = decode_utf8(line, new_byte_pos, advance);
620
+ if (is_space_codepoint(cp)) {
621
+ break;
622
+ }
623
+ move_width += widths[new_char_pos];
624
+ new_char_pos++;
625
+ new_byte_pos += advance;
626
+ }
627
+
628
+ while (new_char_pos < widths.size()) {
629
+ size_t advance = 0;
630
+ char32_t cp = decode_utf8(line, new_byte_pos, advance);
631
+ if (!is_space_codepoint(cp)) {
632
+ break;
633
+ }
634
+ move_width += widths[new_char_pos];
635
+ new_char_pos++;
636
+ new_byte_pos += advance;
637
+ }
638
+
639
+ move_cursor(move_width);
640
+ char_pos = new_char_pos;
641
+ byte_pos = new_byte_pos;
642
+ }
643
+
644
+ static void move_cursor(int delta) {
645
+ if (delta == 0) return;
646
+ #if defined(_WIN32)
647
+ if (hConsole != NULL) {
648
+ CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
649
+ GetConsoleScreenBufferInfo(hConsole, &bufferInfo);
650
+ COORD newCursorPosition = bufferInfo.dwCursorPosition;
651
+ int width = bufferInfo.dwSize.X;
652
+ int newX = newCursorPosition.X + delta;
653
+ int newY = newCursorPosition.Y;
654
+
655
+ while (newX >= width) {
656
+ newX -= width;
657
+ newY++;
658
+ }
659
+ while (newX < 0) {
660
+ newX += width;
661
+ newY--;
662
+ }
663
+
664
+ newCursorPosition.X = newX;
665
+ newCursorPosition.Y = newY;
666
+ SetConsoleCursorPosition(hConsole, newCursorPosition);
667
+ }
668
+ #else
669
+ if (delta < 0) {
670
+ for (int i = 0; i < -delta; i++) fprintf(out, "\b");
671
+ } else {
672
+ for (int i = 0; i < delta; i++) fprintf(out, "\033[C");
673
+ }
674
+ #endif
675
+ }
676
+
677
+ struct history_t {
678
+ std::vector<std::string> entries;
679
+ size_t viewing_idx = SIZE_MAX;
680
+ std::string backup_line; // current line before viewing history
681
+ void add(const std::string & line) {
682
+ if (line.empty()) {
683
+ return;
684
+ }
685
+ // avoid duplicates with the last entry
686
+ if (entries.empty() || entries.back() != line) {
687
+ entries.push_back(line);
688
+ }
689
+ // also clear viewing state
690
+ end_viewing();
691
+ }
692
+ bool prev(std::string & cur_line) {
693
+ if (entries.empty()) {
694
+ return false;
695
+ }
696
+ if (viewing_idx == SIZE_MAX) {
697
+ return false;
698
+ }
699
+ if (viewing_idx > 0) {
700
+ viewing_idx--;
701
+ }
702
+ cur_line = entries[viewing_idx];
703
+ return true;
704
+ }
705
+ bool next(std::string & cur_line) {
706
+ if (entries.empty() || viewing_idx == SIZE_MAX) {
707
+ return false;
708
+ }
709
+ viewing_idx++;
710
+ if (viewing_idx >= entries.size()) {
711
+ cur_line = backup_line;
712
+ end_viewing();
713
+ } else {
714
+ cur_line = entries[viewing_idx];
715
+ }
716
+ return true;
717
+ }
718
+ void begin_viewing(const std::string & line) {
719
+ backup_line = line;
720
+ viewing_idx = entries.size();
721
+ }
722
+ void end_viewing() {
723
+ viewing_idx = SIZE_MAX;
724
+ backup_line.clear();
725
+ }
726
+ bool is_viewing() const {
727
+ return viewing_idx != SIZE_MAX;
728
+ }
729
+ } history;
730
+
355
731
  static bool readline_advanced(std::string & line, bool multiline_input) {
356
732
  if (out != stdout) {
357
733
  fflush(stdout);
@@ -362,8 +738,33 @@ namespace console {
362
738
  bool is_special_char = false;
363
739
  bool end_of_stream = false;
364
740
 
741
+ size_t byte_pos = 0; // current byte index
742
+ size_t char_pos = 0; // current character index (one char can be multiple bytes)
743
+
365
744
  char32_t input_char;
366
745
  while (true) {
746
+ assert(char_pos <= byte_pos);
747
+ assert(char_pos <= widths.size());
748
+ auto history_prev = [&]() {
749
+ if (!history.is_viewing()) {
750
+ history.begin_viewing(line);
751
+ }
752
+ std::string new_line;
753
+ if (!history.prev(new_line)) {
754
+ return;
755
+ }
756
+ set_line_contents(new_line, line, widths, char_pos, byte_pos);
757
+ };
758
+ auto history_next = [&]() {
759
+ if (history.is_viewing()) {
760
+ std::string new_line;
761
+ if (!history.next(new_line)) {
762
+ return;
763
+ }
764
+ set_line_contents(new_line, line, widths, char_pos, byte_pos);
765
+ }
766
+ };
767
+
367
768
  fflush(out); // Ensure all output is displayed before waiting for input
368
769
  input_char = getchar32();
369
770
 
@@ -371,7 +772,7 @@ namespace console {
371
772
  break;
372
773
  }
373
774
 
374
- if (input_char == (char32_t) WEOF || input_char == 0x04 /* Ctrl+D*/) {
775
+ if (input_char == (char32_t) WEOF || input_char == 0x04 /* Ctrl+D */) {
375
776
  end_of_stream = true;
376
777
  break;
377
778
  }
@@ -384,7 +785,71 @@ namespace console {
384
785
 
385
786
  if (input_char == '\033') { // Escape sequence
386
787
  char32_t code = getchar32();
387
- if (code == '[' || code == 0x1B) {
788
+ if (code == '[') {
789
+ std::string params;
790
+ while (true) {
791
+ code = getchar32();
792
+ if ((code >= 'A' && code <= 'Z') || (code >= 'a' && code <= 'z') || code == '~' || code == (char32_t) WEOF) {
793
+ break;
794
+ }
795
+ params.push_back(static_cast<char>(code));
796
+ }
797
+
798
+ const bool ctrl_modifier = has_ctrl_modifier(params);
799
+
800
+ if (code == 'D') { // left
801
+ if (ctrl_modifier) {
802
+ move_word_left(char_pos, byte_pos, widths, line);
803
+ } else if (char_pos > 0) {
804
+ int w = widths[char_pos - 1];
805
+ move_cursor(-w);
806
+ char_pos--;
807
+ byte_pos = prev_utf8_char_pos(line, byte_pos);
808
+ }
809
+ } else if (code == 'C') { // right
810
+ if (ctrl_modifier) {
811
+ move_word_right(char_pos, byte_pos, widths, line);
812
+ } else if (char_pos < widths.size()) {
813
+ int w = widths[char_pos];
814
+ move_cursor(w);
815
+ char_pos++;
816
+ byte_pos = next_utf8_char_pos(line, byte_pos);
817
+ }
818
+ } else if (code == 'H') { // home
819
+ move_to_line_start(char_pos, byte_pos, widths);
820
+ } else if (code == 'F') { // end
821
+ move_to_line_end(char_pos, byte_pos, widths, line);
822
+ } else if (code == 'A' || code == 'B') {
823
+ // up/down
824
+ if (code == 'A') {
825
+ history_prev();
826
+ is_special_char = false;
827
+ } else if (code == 'B') {
828
+ history_next();
829
+ is_special_char = false;
830
+ }
831
+ } else if ((code == '~' || (code >= 'A' && code <= 'Z') || (code >= 'a' && code <= 'z')) && !params.empty()) {
832
+ std::string digits;
833
+ for (char ch : params) {
834
+ if (ch == ';') {
835
+ break;
836
+ }
837
+ if (std::isdigit(static_cast<unsigned char>(ch))) {
838
+ digits.push_back(ch);
839
+ }
840
+ }
841
+
842
+ if (code == '~') {
843
+ if (digits == "1" || digits == "7") { // home
844
+ move_to_line_start(char_pos, byte_pos, widths);
845
+ } else if (digits == "4" || digits == "8") { // end
846
+ move_to_line_end(char_pos, byte_pos, widths, line);
847
+ } else if (digits == "3") { // delete
848
+ delete_at_cursor(line, widths, char_pos, byte_pos);
849
+ }
850
+ }
851
+ }
852
+ } else if (code == 0x1B) {
388
853
  // Discard the rest of the escape sequence
389
854
  while ((code = getchar32()) != (char32_t) WEOF) {
390
855
  if ((code >= 'A' && code <= 'Z') || (code >= 'a' && code <= 'z') || code == '~') {
@@ -392,28 +857,107 @@ namespace console {
392
857
  }
393
858
  }
394
859
  }
860
+ #if defined(_WIN32)
861
+ } else if (input_char == KEY_ARROW_LEFT) {
862
+ if (char_pos > 0) {
863
+ int w = widths[char_pos - 1];
864
+ move_cursor(-w);
865
+ char_pos--;
866
+ byte_pos = prev_utf8_char_pos(line, byte_pos);
867
+ }
868
+ } else if (input_char == KEY_ARROW_RIGHT) {
869
+ if (char_pos < widths.size()) {
870
+ int w = widths[char_pos];
871
+ move_cursor(w);
872
+ char_pos++;
873
+ byte_pos = next_utf8_char_pos(line, byte_pos);
874
+ }
875
+ } else if (input_char == KEY_CTRL_ARROW_LEFT) {
876
+ move_word_left(char_pos, byte_pos, widths, line);
877
+ } else if (input_char == KEY_CTRL_ARROW_RIGHT) {
878
+ move_word_right(char_pos, byte_pos, widths, line);
879
+ } else if (input_char == KEY_HOME) {
880
+ move_to_line_start(char_pos, byte_pos, widths);
881
+ } else if (input_char == KEY_END) {
882
+ move_to_line_end(char_pos, byte_pos, widths, line);
883
+ } else if (input_char == KEY_DELETE) {
884
+ delete_at_cursor(line, widths, char_pos, byte_pos);
885
+ } else if (input_char == KEY_ARROW_UP || input_char == KEY_ARROW_DOWN) {
886
+ if (input_char == KEY_ARROW_UP) {
887
+ history_prev();
888
+ is_special_char = false;
889
+ } else if (input_char == KEY_ARROW_DOWN) {
890
+ history_next();
891
+ is_special_char = false;
892
+ }
893
+ #endif
395
894
  } 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());
895
+ if (char_pos > 0) {
896
+ int w = widths[char_pos - 1];
897
+ move_cursor(-w);
898
+ char_pos--;
899
+ size_t prev_pos = prev_utf8_char_pos(line, byte_pos);
900
+ size_t char_len = byte_pos - prev_pos;
901
+ byte_pos = prev_pos;
902
+
903
+ // remove the character
904
+ line.erase(byte_pos, char_len);
905
+ widths.erase(widths.begin() + char_pos);
906
+
907
+ // redraw tail
908
+ size_t p = byte_pos;
909
+ int tail_width = 0;
910
+ for (size_t i = char_pos; i < widths.size(); ++i) {
911
+ size_t next_p = next_utf8_char_pos(line, p);
912
+ put_codepoint(line.c_str() + p, next_p - p, widths[i]);
913
+ tail_width += widths[i];
914
+ p = next_p;
915
+ }
916
+
917
+ // clear display
918
+ for (int i = 0; i < w; ++i) {
919
+ fputc(' ', out);
920
+ }
921
+ move_cursor(-(tail_width + w));
408
922
  }
409
923
  } 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;
924
+ // insert character
925
+ std::string new_char_str;
926
+ append_utf8(input_char, new_char_str);
927
+ int w = estimateWidth(input_char);
928
+
929
+ if (char_pos == widths.size()) {
930
+ // insert at the end
931
+ line += new_char_str;
932
+ int real_w = put_codepoint(new_char_str.c_str(), new_char_str.length(), w);
933
+ if (real_w < 0) real_w = 0;
934
+ widths.push_back(real_w);
935
+ byte_pos += new_char_str.length();
936
+ char_pos++;
937
+ } else {
938
+ // insert in middle
939
+ line.insert(byte_pos, new_char_str);
940
+
941
+ int real_w = put_codepoint(new_char_str.c_str(), new_char_str.length(), w);
942
+ if (real_w < 0) real_w = 0;
943
+
944
+ widths.insert(widths.begin() + char_pos, real_w);
945
+
946
+ // print the tail
947
+ size_t p = byte_pos + new_char_str.length();
948
+ int tail_width = 0;
949
+ for (size_t i = char_pos + 1; i < widths.size(); ++i) {
950
+ size_t next_p = next_utf8_char_pos(line, p);
951
+ put_codepoint(line.c_str() + p, next_p - p, widths[i]);
952
+ tail_width += widths[i];
953
+ p = next_p;
954
+ }
955
+
956
+ move_cursor(-tail_width);
957
+
958
+ byte_pos += new_char_str.length();
959
+ char_pos++;
415
960
  }
416
- widths.push_back(width);
417
961
  }
418
962
 
419
963
  if (!line.empty() && (line.back() == '\\' || line.back() == '/')) {
@@ -451,6 +995,15 @@ namespace console {
451
995
  }
452
996
  }
453
997
 
998
+ if (!end_of_stream && !line.empty()) {
999
+ // remove the trailing newline for history storage
1000
+ if (!line.empty() && line.back() == '\n') {
1001
+ line.pop_back();
1002
+ }
1003
+ // TODO: maybe support multiline history entries?
1004
+ history.add(line);
1005
+ }
1006
+
454
1007
  fflush(out);
455
1008
  return has_more;
456
1009
  }