@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.
- package/lib/binding.ts +8 -0
- package/package.json +15 -15
- package/scripts/llama.cpp.patch +25 -26
- package/src/LlamaContext.cpp +2 -2
- package/src/llama.cpp/common/CMakeLists.txt +2 -0
- package/src/llama.cpp/common/arg.cpp +364 -193
- package/src/llama.cpp/common/arg.h +43 -2
- 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-peg-parser.cpp +16 -2
- package/src/llama.cpp/common/chat.cpp +272 -0
- package/src/llama.cpp/common/common.cpp +130 -67
- package/src/llama.cpp/common/common.h +40 -16
- package/src/llama.cpp/common/console.cpp +680 -47
- package/src/llama.cpp/common/console.h +30 -8
- package/src/llama.cpp/common/download.cpp +69 -25
- package/src/llama.cpp/common/json-schema-to-grammar.cpp +132 -3
- package/src/llama.cpp/common/json-schema-to-grammar.h +20 -0
- package/src/llama.cpp/common/log.cpp +5 -0
- package/src/llama.cpp/common/log.h +1 -0
- package/src/llama.cpp/common/peg-parser.cpp +1 -1
- package/src/llama.cpp/common/preset.cpp +206 -0
- package/src/llama.cpp/common/preset.h +32 -0
- package/src/llama.cpp/common/sampling.cpp +91 -92
- package/src/llama.cpp/common/sampling.h +11 -6
- package/src/llama.cpp/common/speculative.cpp +1 -1
- package/src/llama.cpp/ggml/CMakeLists.txt +5 -0
- package/src/llama.cpp/ggml/include/ggml-alloc.h +9 -0
- package/src/llama.cpp/ggml/include/ggml-backend.h +1 -0
- package/src/llama.cpp/ggml/include/ggml-cpu.h +1 -0
- package/src/llama.cpp/ggml/include/ggml.h +7 -8
- package/src/llama.cpp/ggml/src/CMakeLists.txt +3 -0
- package/src/llama.cpp/ggml/src/ggml-cpu/CMakeLists.txt +3 -0
- package/src/llama.cpp/ggml/src/ggml-cpu/arch/arm/repack.cpp +2 -0
- package/src/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c +69 -39
- package/src/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.cpp +4 -0
- package/src/llama.cpp/ggml/src/ggml-cpu/repack.cpp +2 -1
- package/src/llama.cpp/include/llama.h +18 -1
- package/src/llama.cpp/src/CMakeLists.txt +2 -1
- package/src/llama.cpp/src/llama-arch.cpp +1890 -2248
- package/src/llama.cpp/src/llama-arch.h +9 -2
- package/src/llama.cpp/src/llama-batch.cpp +12 -2
- package/src/llama.cpp/src/llama-batch.h +4 -2
- package/src/llama.cpp/src/llama-context.cpp +99 -29
- package/src/llama.cpp/src/llama-context.h +9 -3
- 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 +85 -17
- package/src/llama.cpp/src/llama-graph.h +17 -4
- package/src/llama.cpp/src/llama-hparams.cpp +6 -0
- package/src/llama.cpp/src/llama-hparams.h +5 -1
- package/src/llama.cpp/src/llama-impl.cpp +4 -0
- package/src/llama.cpp/src/llama-kv-cache.cpp +90 -42
- package/src/llama.cpp/src/llama-kv-cache.h +19 -2
- package/src/llama.cpp/src/llama-memory-hybrid.cpp +1 -1
- package/src/llama.cpp/src/llama-model-loader.cpp +2 -0
- package/src/llama.cpp/src/llama-model-loader.h +2 -0
- package/src/llama.cpp/src/llama-model.cpp +123 -52
- package/src/llama.cpp/src/llama-model.h +1 -0
- package/src/llama.cpp/src/llama-quant.cpp +1 -1
- package/src/llama.cpp/src/llama-vocab.cpp +2 -1
- package/src/llama.cpp/src/llama.cpp +675 -1
- package/src/llama.cpp/src/models/deepseek2.cpp +9 -5
- package/src/llama.cpp/src/models/{gemma3-iswa.cpp → gemma3.cpp} +30 -5
- package/src/llama.cpp/src/models/glm4-moe.cpp +28 -11
- package/src/llama.cpp/src/models/glm4.cpp +27 -4
- package/src/llama.cpp/src/models/models.h +8 -7
- package/src/llama.cpp/src/models/nemotron-h.cpp +35 -6
- package/src/llama.cpp/src/models/qwen2.cpp +12 -3
- 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
|
|
43
|
-
static bool
|
|
44
|
-
static
|
|
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*
|
|
74
|
+
static FILE* out = stdout;
|
|
47
75
|
|
|
48
76
|
#if defined (_WIN32)
|
|
49
|
-
static void*
|
|
77
|
+
static void* hConsole;
|
|
50
78
|
#else
|
|
51
|
-
static FILE*
|
|
52
|
-
static termios
|
|
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(
|
|
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(
|
|
171
|
+
void set_display(display_type display) {
|
|
144
172
|
if (advanced_display && current_display != display) {
|
|
145
|
-
|
|
173
|
+
common_log_flush(common_log_main());
|
|
146
174
|
switch(display) {
|
|
147
|
-
case
|
|
175
|
+
case DISPLAY_TYPE_RESET:
|
|
148
176
|
fprintf(out, ANSI_COLOR_RESET);
|
|
149
177
|
break;
|
|
150
|
-
case
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
340
|
-
if (
|
|
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
|
|
576
|
+
size_t new_char_pos = char_pos;
|
|
577
|
+
size_t new_byte_pos = byte_pos;
|
|
578
|
+
int move_width = 0;
|
|
345
579
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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 == '['
|
|
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 (
|
|
397
|
-
int
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
}
|