@fugood/llama.node 1.4.5 → 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.
- package/lib/binding.ts +49 -0
- package/lib/index.js +13 -0
- package/lib/index.ts +13 -0
- package/package.json +15 -15
- package/scripts/llama.cpp.patch +8 -8
- package/src/LlamaContext.cpp +69 -0
- package/src/LlamaContext.h +3 -0
- package/src/llama.cpp/common/chat-parser-xml-toolcall.cpp +36 -18
- package/src/llama.cpp/common/chat-parser-xml-toolcall.h +1 -1
- package/src/llama.cpp/common/chat-parser.cpp +3 -2
- package/src/llama.cpp/common/chat.cpp +132 -0
- package/src/llama.cpp/common/console.cpp +582 -29
- package/src/llama.cpp/ggml/CMakeLists.txt +1 -0
- package/src/llama.cpp/ggml/src/ggml-cpu/CMakeLists.txt +3 -0
- package/src/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c +9 -0
- package/src/llama.cpp/src/CMakeLists.txt +2 -1
- package/src/llama.cpp/src/llama-context.cpp +6 -6
- package/src/llama.cpp/src/llama-context.h +1 -1
- package/src/llama.cpp/src/llama-grammar.cpp +233 -33
- package/src/llama.cpp/src/llama-grammar.h +20 -1
- package/src/llama.cpp/src/llama-graph.cpp +1 -1
- package/src/llama.cpp/src/llama-model.cpp +20 -8
- package/src/llama.cpp/src/models/{gemma3-iswa.cpp → gemma3.cpp} +30 -5
- package/src/llama.cpp/src/models/models.h +3 -2
|
@@ -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
|
-
|
|
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
|
|
340
|
-
if (
|
|
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
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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 == '['
|
|
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 (
|
|
397
|
-
int
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
}
|