char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
506200cf8
char/aliceai-t5-moe
1#include "console.h" 2#include "log.h" 3#include <vector> 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> 14 15#if defined(_WIN32 ) 16#define WIN32_LEAN_AND_MEAN 17#ifndef NOMINMAX 18#define NOMINMAX 19#endif 20#include <windows.h> 21#include <fcntl.h> 22#include <io.h> 23#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING 24#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 25#endif 26#else 27#include <climits> 28#include <sys/ioctl.h> 29#include <unistd.h> 30#include <wchar.h> 31#include <stdio.h> 32#include <stdlib.h> 33#include <signal.h> 34#include <termios.h> 35#endif 36 37#define ANSI_COLOR_RED "\x1b[31m" 38#define ANSI_COLOR_GREEN "\x1b[32m" 39#define ANSI_COLOR_YELLOW "\x1b[33m" 40#define ANSI_COLOR_BLUE "\x1b[34m" 41#define ANSI_COLOR_MAGENTA "\x1b[35m" 42#define ANSI_COLOR_CYAN "\x1b[36m" 43#define ANSI_COLOR_GRAY "\x1b[90m" 44#define ANSI_COLOR_RESET "\x1b[0m" 45#define ANSI_BOLD "\x1b[1m" 46 47namespace console { 48 49#if defined (_WIN32 ) 50namespace { 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. 54static constexpr char32_t KEY_ARROW_LEFT = 0xE000 ; 55static constexpr char32_t KEY_ARROW_RIGHT = 0xE001 ; 56static constexpr char32_t KEY_ARROW_UP = 0xE002 ; 57static constexpr char32_t KEY_ARROW_DOWN = 0xE003 ; 58static constexpr char32_t KEY_HOME = 0xE004 ; 59static constexpr char32_t KEY_END = 0xE005 ; 60static constexpr char32_t KEY_CTRL_ARROW_LEFT = 0xE006 ; 61static constexpr char32_t KEY_CTRL_ARROW_RIGHT = 0xE007 ; 62static constexpr char32_t KEY_DELETE = 0xE008 ; 63 } 64 65// 66// Console state 67// 68#endif 69 70static bool advanced_display = false; 71static bool simple_io = true; 72static display_type current_display = DISPLAY_TYPE_RESET ; 73 74static FILE * out = stdout ; 75 76#if defined (_WIN32 ) 77static void * hConsole ; 78#else 79static FILE * tty = nullptr ; 80static termios initial_state ; 81#endif 82 83static completion_callback completion_cb = nullptr ; 84 85// 86// Init and cleanup 87// 88 89void init (bool use_simple_io ,bool use_advanced_display ) { 90advanced_display = use_advanced_display ; 91simple_io = use_simple_io ; 92#if defined(_WIN32 ) 93// Windows-specific console initialization 94DWORD dwMode = 0 ; 95hConsole = GetStdHandle (STD_OUTPUT_HANDLE ); 96if (hConsole == INVALID_HANDLE_VALUE || !GetConsoleMode (hConsole ,& dwMode )) { 97hConsole = GetStdHandle (STD_ERROR_HANDLE ); 98if (hConsole != INVALID_HANDLE_VALUE && (!GetConsoleMode (hConsole ,& dwMode ))) { 99hConsole = nullptr ; 100simple_io = true; 101 } 102 } 103if (hConsole ) { 104// Check conditions combined to reduce nesting 105if (advanced_display && !(dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING )&& 106 !SetConsoleMode (hConsole ,dwMode |ENABLE_VIRTUAL_TERMINAL_PROCESSING )) { 107advanced_display = false; 108 } 109// Set console output codepage to UTF8 110SetConsoleOutputCP (CP_UTF8 ); 111 } 112HANDLE hConIn = GetStdHandle (STD_INPUT_HANDLE ); 113if (hConIn != INVALID_HANDLE_VALUE && GetConsoleMode (hConIn ,& dwMode )) { 114// Set console input codepage to UTF16 115_setmode (_fileno (stdin ),_O_WTEXT ); 116 117// Set ICANON (ENABLE_LINE_INPUT) and ECHO (ENABLE_ECHO_INPUT) 118if (simple_io ) { 119dwMode |=ENABLE_LINE_INPUT |ENABLE_ECHO_INPUT ; 120 }else { 121dwMode &= ~(ENABLE_LINE_INPUT |ENABLE_ECHO_INPUT ); 122 } 123if (!SetConsoleMode (hConIn ,dwMode )) { 124simple_io = true; 125 } 126 } 127if (simple_io ) { 128_setmode (_fileno (stdin ),_O_U8TEXT ); 129 } 130#else 131// POSIX-specific console initialization 132if (!simple_io ) { 133struct termios new_termios ; 134tcgetattr (STDIN_FILENO ,& initial_state ); 135new_termios = initial_state ; 136new_termios .c_lflag &= ~(ICANON |ECHO ); 137new_termios .c_cc [VMIN ]= 1 ; 138new_termios .c_cc [VTIME ]= 0 ; 139tcsetattr (STDIN_FILENO ,TCSANOW ,& new_termios ); 140 141tty = fopen ("/dev/tty" ,"w+" ); 142if (tty != nullptr ) { 143out = tty ; 144 } 145 } 146 147setlocale (LC_ALL ,"" ); 148#endif 149 } 150 151void cleanup () { 152// Reset console display 153set_display (DISPLAY_TYPE_RESET ); 154 155#if !defined(_WIN32 ) 156// Restore settings on POSIX systems 157if (!simple_io ) { 158if (tty != nullptr ) { 159out = stdout ; 160fclose (tty ); 161tty = nullptr ; 162 } 163tcsetattr (STDIN_FILENO ,TCSANOW ,& initial_state ); 164 } 165#endif 166 } 167 168// 169// Display and IO 170// 171 172// Keep track of current display and only emit ANSI code if it changes 173void set_display (display_type display ) { 174if (advanced_display && current_display != display ) { 175common_log_flush (common_log_main ()); 176switch (display ) { 177case DISPLAY_TYPE_RESET : 178fprintf (out ,ANSI_COLOR_RESET ); 179break ; 180case DISPLAY_TYPE_INFO : 181fprintf (out ,ANSI_COLOR_MAGENTA ); 182break ; 183case DISPLAY_TYPE_PROMPT : 184fprintf (out ,ANSI_COLOR_YELLOW ); 185break ; 186case DISPLAY_TYPE_REASONING : 187fprintf (out ,ANSI_COLOR_GRAY ); 188break ; 189case DISPLAY_TYPE_USER_INPUT : 190fprintf (out ,ANSI_BOLD ANSI_COLOR_GREEN ); 191break ; 192case DISPLAY_TYPE_ERROR : 193fprintf (out ,ANSI_BOLD ANSI_COLOR_RED ); 194 } 195current_display = display ; 196fflush (out ); 197 } 198 } 199 200static char32_t getchar32 () { 201#if defined(_WIN32 ) 202HANDLE hConsole = GetStdHandle (STD_INPUT_HANDLE ); 203wchar_t high_surrogate = 0 ; 204 205while (true) { 206INPUT_RECORD record ; 207DWORD count ; 208if (!ReadConsoleInputW (hConsole ,& record ,1 ,& count )|| count == 0 ) { 209return WEOF ; 210 } 211 212if (record .EventType == KEY_EVENT && record .Event .KeyEvent .bKeyDown ) { 213wchar_t wc = record .Event .KeyEvent .uChar .UnicodeChar ; 214if (wc == 0 ) { 215const DWORD ctrl_mask = LEFT_CTRL_PRESSED |RIGHT_CTRL_PRESSED ; 216const bool ctrl_pressed = (record .Event .KeyEvent .dwControlKeyState & ctrl_mask )!= 0 ; 217switch (record .Event .KeyEvent .wVirtualKeyCode ) { 218case VK_LEFT :return ctrl_pressed ?KEY_CTRL_ARROW_LEFT :KEY_ARROW_LEFT ; 219case VK_RIGHT :return ctrl_pressed ?KEY_CTRL_ARROW_RIGHT :KEY_ARROW_RIGHT ; 220case VK_UP :return KEY_ARROW_UP ; 221case VK_DOWN :return KEY_ARROW_DOWN ; 222case VK_HOME :return KEY_HOME ; 223case VK_END :return KEY_END ; 224case VK_DELETE :return KEY_DELETE ; 225default :continue ; 226 } 227 } 228 229if ((wc >=0xD800 )&& (wc <=0xDBFF )) {// Check if wc is a high surrogate 230high_surrogate = wc ; 231continue ; 232 } 233if ((wc >=0xDC00 )&& (wc <=0xDFFF )) {// Check if wc is a low surrogate 234if (high_surrogate != 0 ) {// Check if we have a high surrogate 235return ((high_surrogate - 0xD800 ) <<10 )+ (wc - 0xDC00 )+ 0x10000 ; 236 } 237 } 238 239high_surrogate = 0 ;// Reset the high surrogate 240return static_cast < char32_t > (wc ); 241 } 242 } 243#else 244wchar_t wc = getwchar (); 245if (static_cast < wint_t > (wc )== WEOF ) { 246return WEOF ; 247 } 248 249#if WCHAR_MAX == 0xFFFF 250if ((wc >=0xD800 )&& (wc <=0xDBFF )) {// Check if wc is a high surrogate 251wchar_t low_surrogate = getwchar (); 252if ((low_surrogate >=0xDC00 )&& (low_surrogate <=0xDFFF )) {// Check if the next wchar is a low surrogate 253return (static_cast < char32_t > (wc & 0x03FF ) <<10 )+ (low_surrogate & 0x03FF )+ 0x10000 ; 254 } 255 } 256if ((wc >=0xD800 )&& (wc <=0xDFFF )) {// Invalid surrogate pair 257return 0xFFFD ;// Return the replacement character U+FFFD 258 } 259#endif 260 261return static_cast < char32_t > (wc ); 262#endif 263 } 264 265static void pop_cursor () { 266#if defined(_WIN32 ) 267if (hConsole != NULL ) { 268CONSOLE_SCREEN_BUFFER_INFO bufferInfo ; 269GetConsoleScreenBufferInfo (hConsole ,& bufferInfo ); 270 271COORD newCursorPosition = bufferInfo .dwCursorPosition ; 272if (newCursorPosition .X == 0 ) { 273newCursorPosition .X = bufferInfo .dwSize .X - 1 ; 274newCursorPosition .Y -= 1 ; 275 }else { 276newCursorPosition .X -= 1 ; 277 } 278 279SetConsoleCursorPosition (hConsole ,newCursorPosition ); 280return ; 281 } 282#endif 283putc ('\b' ,out ); 284 } 285 286static int estimateWidth (char32_t codepoint ) { 287#if defined(_WIN32 ) 288 (void )codepoint ; 289return 1 ; 290#else 291return wcwidth (codepoint ); 292#endif 293 } 294 295static int put_codepoint (const char * utf8_codepoint ,size_t length ,int expectedWidth ) { 296#if defined(_WIN32 ) 297CONSOLE_SCREEN_BUFFER_INFO bufferInfo ; 298if (!GetConsoleScreenBufferInfo (hConsole ,& bufferInfo )) { 299// go with the default 300return expectedWidth ; 301 } 302COORD initialPosition = bufferInfo .dwCursorPosition ; 303DWORD nNumberOfChars = length ; 304WriteConsole (hConsole ,utf8_codepoint ,nNumberOfChars ,& nNumberOfChars ,NULL ); 305 306CONSOLE_SCREEN_BUFFER_INFO newBufferInfo ; 307GetConsoleScreenBufferInfo (hConsole ,& newBufferInfo ); 308 309// Figure out our real position if we're in the last column 310if (utf8_codepoint [0 ]!= 0x09 && initialPosition .X == newBufferInfo .dwSize .X - 1 ) { 311DWORD nNumberOfChars ; 312WriteConsole (hConsole ,& " \b" ,2 ,& nNumberOfChars ,NULL ); 313GetConsoleScreenBufferInfo (hConsole ,& newBufferInfo ); 314 } 315 316int width = newBufferInfo .dwCursorPosition .X - initialPosition .X ; 317if (width < 0 ) { 318width += newBufferInfo .dwSize .X ; 319 } 320return width ; 321#else 322// We can trust expectedWidth if we've got one 323if (expectedWidth >=0 || tty == nullptr ) { 324fwrite (utf8_codepoint ,length ,1 ,out ); 325return expectedWidth ; 326 } 327 328fputs ("\033[6n" ,tty );// Query cursor position 329int x1 ; 330int y1 ; 331int x2 ; 332int y2 ; 333int results = 0 ; 334results = fscanf (tty ,"\033[%d;%dR" ,& y1 ,& x1 ); 335 336fwrite (utf8_codepoint ,length ,1 ,tty ); 337 338fputs ("\033[6n" ,tty );// Query cursor position 339results += fscanf (tty ,"\033[%d;%dR" ,& y2 ,& x2 ); 340 341if (results != 4 ) { 342return expectedWidth ; 343 } 344 345int width = x2 - x1 ; 346if (width < 0 ) { 347// Calculate the width considering text wrapping 348struct winsize w ; 349ioctl (STDOUT_FILENO ,TIOCGWINSZ ,& w ); 350width += w .ws_col ; 351 } 352return width ; 353#endif 354 } 355 356static void replace_last (char ch ) { 357#if defined(_WIN32 ) 358pop_cursor (); 359put_codepoint (& ch ,1 ,1 ); 360#else 361fprintf (out ,"\b%c" ,ch ); 362#endif 363 } 364 365static char32_t decode_utf8 (const std::string & input ,size_t pos ,size_t & advance ) { 366unsigned char c = static_cast < unsigned char > (input [pos ]); 367if ((c & 0x80u )== 0u ) { 368advance = 1 ; 369return c ; 370 } 371if ((c & 0xE0u )== 0xC0u && pos + 1 < input .size ()) { 372unsigned char c1 = static_cast < unsigned char > (input [pos + 1 ]); 373if ((c1 & 0xC0u )!= 0x80u ) { 374advance = 1 ; 375return 0xFFFD ; 376 } 377advance = 2 ; 378return ((c & 0x1Fu ) <<6 ) | (static_cast < unsigned char > (input [pos + 1 ])& 0x3Fu ); 379 } 380if ((c & 0xF0u )== 0xE0u && pos + 2 < input .size ()) { 381unsigned char c1 = static_cast < unsigned char > (input [pos + 1 ]); 382unsigned char c2 = static_cast < unsigned char > (input [pos + 2 ]); 383if ((c1 & 0xC0u )!= 0x80u || (c2 & 0xC0u )!= 0x80u ) { 384advance = 1 ; 385return 0xFFFD ; 386 } 387advance = 3 ; 388return ((c & 0x0Fu ) <<12 ) | 389 ((static_cast < unsigned char > (input [pos + 1 ])& 0x3Fu ) <<6 ) | 390 (static_cast < unsigned char > (input [pos + 2 ])& 0x3Fu ); 391 } 392if ((c & 0xF8u )== 0xF0u && pos + 3 < input .size ()) { 393unsigned char c1 = static_cast < unsigned char > (input [pos + 1 ]); 394unsigned char c2 = static_cast < unsigned char > (input [pos + 2 ]); 395unsigned char c3 = static_cast < unsigned char > (input [pos + 3 ]); 396if ((c1 & 0xC0u )!= 0x80u || (c2 & 0xC0u )!= 0x80u || (c3 & 0xC0u )!= 0x80u ) { 397advance = 1 ; 398return 0xFFFD ; 399 } 400advance = 4 ; 401return ((c & 0x07u ) <<18 ) | 402 ((static_cast < unsigned char > (input [pos + 1 ])& 0x3Fu ) <<12 ) | 403 ((static_cast < unsigned char > (input [pos + 2 ])& 0x3Fu ) <<6 ) | 404 (static_cast < unsigned char > (input [pos + 3 ])& 0x3Fu ); 405 } 406 407advance = 1 ; 408return 0xFFFD ;// replacement character for invalid input 409 } 410 411static void append_utf8 (char32_t ch , std::string & out ) { 412if (ch <=0x7F ) { 413out .push_back (static_cast < unsigned char > (ch )); 414 }else if (ch <=0x7FF ) { 415out .push_back (static_cast < unsigned char > (0xC0 | ((ch >>6 )& 0x1F ))); 416out .push_back (static_cast < unsigned char > (0x80 | (ch & 0x3F ))); 417 }else if (ch <=0xFFFF ) { 418out .push_back (static_cast < unsigned char > (0xE0 | ((ch >>12 )& 0x0F ))); 419out .push_back (static_cast < unsigned char > (0x80 | ((ch >>6 )& 0x3F ))); 420out .push_back (static_cast < unsigned char > (0x80 | (ch & 0x3F ))); 421 }else if (ch <=0x10FFFF ) { 422out .push_back (static_cast < unsigned char > (0xF0 | ((ch >>18 )& 0x07 ))); 423out .push_back (static_cast < unsigned char > (0x80 | ((ch >>12 )& 0x3F ))); 424out .push_back (static_cast < unsigned char > (0x80 | ((ch >>6 )& 0x3F ))); 425out .push_back (static_cast < unsigned char > (0x80 | (ch & 0x3F ))); 426 }else { 427// Invalid Unicode code point 428 } 429 } 430 431// Helper function to remove the last UTF-8 character from a string 432static size_t prev_utf8_char_pos (const std::string & line ,size_t pos ) { 433if (pos == 0 )return 0 ; 434pos -- ; 435while (pos > 0 && (line [pos ]& 0xC0 )== 0x80 ) { 436pos -- ; 437 } 438return pos ; 439 } 440 441static size_t next_utf8_char_pos (const std::string & line ,size_t pos ) { 442if (pos >=line .length ())return line .length (); 443pos ++ ; 444while (pos < line .length ()&& (line [pos ]& 0xC0 )== 0x80 ) { 445pos ++ ; 446 } 447return pos ; 448 } 449 450static void move_cursor (int delta ); 451static void move_word_left (size_t & char_pos ,size_t & byte_pos ,const std::vector < int > & widths ,const std::string & line ); 452static void move_word_right (size_t & char_pos ,size_t & byte_pos ,const std::vector < int > & widths ,const std::string & line ); 453static void move_to_line_start (size_t & char_pos ,size_t & byte_pos ,const std::vector < int > & widths ); 454static void move_to_line_end (size_t & char_pos ,size_t & byte_pos ,const std::vector < int > & widths ,const std::string & line ); 455 456static void delete_at_cursor (std::string & line , std::vector < int > & widths ,size_t & char_pos ,size_t & byte_pos ) { 457if (char_pos >=widths .size ()) { 458return ; 459 } 460 461size_t next_pos = next_utf8_char_pos (line ,byte_pos ); 462int w = widths [char_pos ]; 463size_t char_len = next_pos - byte_pos ; 464 465line .erase (byte_pos ,char_len ); 466widths .erase (widths .begin ()+ char_pos ); 467 468size_t p = byte_pos ; 469int tail_width = 0 ; 470for (size_t i = char_pos ;i < widths .size ();++ i ) { 471size_t following = next_utf8_char_pos (line ,p ); 472put_codepoint (line .c_str ()+ p ,following - p ,widths [i ]); 473tail_width += widths [i ]; 474p = following ; 475 } 476 477for (int i = 0 ;i < w ;++ i ) { 478fputc (' ' ,out ); 479 } 480 481move_cursor (- (tail_width + w )); 482 } 483 484static void clear_current_line (const std::vector < int > & widths ) { 485int total_width = 0 ; 486for (int w :widths ) { 487total_width += (w > 0 ?w :1 ); 488 } 489 490if (total_width > 0 ) { 491 std::string spaces (total_width ,' ' ); 492fwrite (spaces .c_str (),1 ,total_width ,out ); 493move_cursor (- total_width ); 494 } 495 } 496 497static void set_line_contents (std::string new_line , std::string & line , std::vector < int > & widths ,size_t & char_pos , 498size_t & byte_pos ,int cursor_byte_pos = -1 ) { 499move_to_line_start (char_pos ,byte_pos ,widths ); 500clear_current_line (widths ); 501 502line = std::move (new_line ); 503widths .clear (); 504byte_pos = 0 ; 505char_pos = 0 ; 506 507size_t idx = 0 ; 508int back_width = 0 ; 509while (idx < line .size ()) { 510size_t advance = 0 ; 511char32_t cp = decode_utf8 (line ,idx ,advance ); 512int expected_width = estimateWidth (cp ); 513int real_width = put_codepoint (line .c_str ()+ idx ,advance ,expected_width ); 514if (real_width < 0 )real_width = 0 ; 515widths .push_back (real_width ); 516idx += advance ; 517if (cursor_byte_pos >=0 && static_cast < size_t > (cursor_byte_pos )< idx ) { 518back_width += real_width ; 519 }else { 520++ char_pos ; 521byte_pos = idx ; 522 } 523 } 524if (cursor_byte_pos >=0 ) { 525move_cursor (- back_width ); 526 } 527 } 528 529static void move_to_line_start (size_t & char_pos ,size_t & byte_pos ,const std::vector < int > & widths ) { 530int back_width = 0 ; 531for (size_t i = 0 ;i < char_pos ;++ i ) { 532back_width += widths [i ]; 533 } 534move_cursor (- back_width ); 535char_pos = 0 ; 536byte_pos = 0 ; 537 } 538 539static void move_to_line_end (size_t & char_pos ,size_t & byte_pos ,const std::vector < int > & widths ,const std::string & line ) { 540int forward_width = 0 ; 541for (size_t i = char_pos ;i < widths .size ();++ i ) { 542forward_width += widths [i ]; 543 } 544move_cursor (forward_width ); 545char_pos = widths .size (); 546byte_pos = line .length (); 547 } 548 549static bool has_ctrl_modifier (const std::string & params ) { 550size_t start = 0 ; 551while (start < params .size ()) { 552size_t end = params .find (';' ,start ); 553size_t len = (end == std::string::npos ) ?params .size ()- start :end - start ; 554if (len > 0 ) { 555int value = 0 ; 556for (size_t i = 0 ;i < len ;++ i ) { 557char ch = params [start + i ]; 558if (!std::isdigit (static_cast < unsigned char > (ch ))) { 559value = -1 ; 560break ; 561 } 562value = value * 10 + (ch - '0' ); 563 } 564if (value == 5 ) { 565return true; 566 } 567 } 568 569if (end == std::string::npos ) { 570break ; 571 } 572start = end + 1 ; 573 } 574return false; 575 } 576 577static bool is_space_codepoint (char32_t cp ) { 578return std::iswspace (static_cast < wint_t > (cp ))!= 0 ; 579 } 580 581static void move_word_left (size_t & char_pos ,size_t & byte_pos ,const std::vector < int > & widths ,const std::string & line ) { 582if (char_pos == 0 ) { 583return ; 584 } 585 586size_t new_char_pos = char_pos ; 587size_t new_byte_pos = byte_pos ; 588int move_width = 0 ; 589 590while (new_char_pos > 0 ) { 591size_t prev_byte = prev_utf8_char_pos (line ,new_byte_pos ); 592size_t advance = 0 ; 593char32_t cp = decode_utf8 (line ,prev_byte ,advance ); 594if (!is_space_codepoint (cp )) { 595break ; 596 } 597move_width += widths [new_char_pos - 1 ]; 598new_char_pos -- ; 599new_byte_pos = prev_byte ; 600 } 601 602while (new_char_pos > 0 ) { 603size_t prev_byte = prev_utf8_char_pos (line ,new_byte_pos ); 604size_t advance = 0 ; 605char32_t cp = decode_utf8 (line ,prev_byte ,advance ); 606if (is_space_codepoint (cp )) { 607break ; 608 } 609move_width += widths [new_char_pos - 1 ]; 610new_char_pos -- ; 611new_byte_pos = prev_byte ; 612 } 613 614move_cursor (- move_width ); 615char_pos = new_char_pos ; 616byte_pos = new_byte_pos ; 617 } 618 619static void move_word_right (size_t & char_pos ,size_t & byte_pos ,const std::vector < int > & widths ,const std::string & line ) { 620if (char_pos >=widths .size ()) { 621return ; 622 } 623 624size_t new_char_pos = char_pos ; 625size_t new_byte_pos = byte_pos ; 626int move_width = 0 ; 627 628while (new_char_pos < widths .size ()) { 629size_t advance = 0 ; 630char32_t cp = decode_utf8 (line ,new_byte_pos ,advance ); 631if (!is_space_codepoint (cp )) { 632break ; 633 } 634move_width += widths [new_char_pos ]; 635new_char_pos ++ ; 636new_byte_pos += advance ; 637 } 638 639while (new_char_pos < widths .size ()) { 640size_t advance = 0 ; 641char32_t cp = decode_utf8 (line ,new_byte_pos ,advance ); 642if (is_space_codepoint (cp )) { 643break ; 644 } 645move_width += widths [new_char_pos ]; 646new_char_pos ++ ; 647new_byte_pos += advance ; 648 } 649 650while (new_char_pos < widths .size ()) { 651size_t advance = 0 ; 652char32_t cp = decode_utf8 (line ,new_byte_pos ,advance ); 653if (!is_space_codepoint (cp )) { 654break ; 655 } 656move_width += widths [new_char_pos ]; 657new_char_pos ++ ; 658new_byte_pos += advance ; 659 } 660 661move_cursor (move_width ); 662char_pos = new_char_pos ; 663byte_pos = new_byte_pos ; 664 } 665 666static void move_cursor (int delta ) { 667if (delta == 0 )return ; 668#if defined(_WIN32 ) 669if (hConsole != NULL ) { 670CONSOLE_SCREEN_BUFFER_INFO bufferInfo ; 671GetConsoleScreenBufferInfo (hConsole ,& bufferInfo ); 672COORD newCursorPosition = bufferInfo .dwCursorPosition ; 673int width = bufferInfo .dwSize .X ; 674int newX = newCursorPosition .X + delta ; 675int newY = newCursorPosition .Y ; 676 677while (newX >=width ) { 678newX -= width ; 679newY ++ ; 680 } 681while (newX < 0 ) { 682newX += width ; 683newY -- ; 684 } 685 686newCursorPosition .X = newX ; 687newCursorPosition .Y = newY ; 688SetConsoleCursorPosition (hConsole ,newCursorPosition ); 689 } 690#else 691if (delta < 0 ) { 692for (int i = 0 ;i < - delta ;i ++ )fprintf (out ,"\b" ); 693 }else { 694for (int i = 0 ;i < delta ;i ++ )fprintf (out ,"\033[C" ); 695 } 696#endif 697 } 698 699struct history_t { 700 std::vector < std::string > entries ; 701size_t viewing_idx = SIZE_MAX ; 702 std::string backup_line ;// current line before viewing history 703void add (std::string_view line ) { 704if (line .empty ()) { 705return ; 706 } 707// avoid duplicates with the last entry 708if (entries .empty ()|| entries .back ()!= line ) { 709entries .emplace_back (line ); 710 } 711// also clear viewing state 712end_viewing (); 713 } 714bool prev (std::string & cur_line ) { 715if (entries .empty ()) { 716return false; 717 } 718if (viewing_idx == SIZE_MAX ) { 719return false; 720 } 721if (viewing_idx > 0 ) { 722viewing_idx -- ; 723 } 724cur_line = entries [viewing_idx ]; 725return true; 726 } 727bool next (std::string & cur_line ) { 728if (entries .empty ()|| viewing_idx == SIZE_MAX ) { 729return false; 730 } 731viewing_idx ++ ; 732if (viewing_idx >=entries .size ()) { 733cur_line = backup_line ; 734end_viewing (); 735 }else { 736cur_line = entries [viewing_idx ]; 737 } 738return true; 739 } 740void begin_viewing (const std::string & line ) { 741backup_line = line ; 742viewing_idx = entries .size (); 743 } 744void end_viewing () { 745viewing_idx = SIZE_MAX ; 746backup_line .clear (); 747 } 748bool is_viewing ()const { 749return viewing_idx != SIZE_MAX ; 750 } 751 }history ; 752 753static bool readline_advanced (std::string & line ,bool multiline_input ) { 754if (out != stdout ) { 755fflush (stdout ); 756 } 757 758line .clear (); 759 std::vector < int > widths ; 760bool is_special_char = false; 761bool end_of_stream = false; 762 763size_t byte_pos = 0 ;// current byte index 764size_t char_pos = 0 ;// current character index (one char can be multiple bytes) 765 766char32_t input_char ; 767while (true) { 768assert (char_pos <=byte_pos ); 769assert (char_pos <=widths .size ()); 770auto history_prev = [& ]() { 771if (!history .is_viewing ()) { 772history .begin_viewing (line ); 773 } 774 std::string new_line ; 775if (!history .prev (new_line )) { 776return ; 777 } 778set_line_contents (new_line ,line ,widths ,char_pos ,byte_pos ); 779 }; 780auto history_next = [& ]() { 781if (history .is_viewing ()) { 782 std::string new_line ; 783if (!history .next (new_line )) { 784return ; 785 } 786set_line_contents (new_line ,line ,widths ,char_pos ,byte_pos ); 787 } 788 }; 789 790fflush (out );// Ensure all output is displayed before waiting for input 791input_char = getchar32 (); 792 793if (input_char == '\r' || input_char == '\n' ) { 794break ; 795 } 796 797if (completion_cb && input_char == '\t' ) { 798auto candidates = completion_cb (line ,byte_pos ); 799 800if (!candidates .empty ()) { 801if (candidates .size ()> 1 || candidates [0 ].first != line ) { 802// TODO?: Display all candidates 803set_line_contents (candidates [0 ].first ,line ,widths ,char_pos ,byte_pos ,candidates [0 ].second ); 804 }else { 805// TODO: Move cursor to new byte_pos 806 } 807continue ; 808 } 809 } 810 811if (input_char == (char32_t )WEOF || input_char == 0x04 /* Ctrl+D */ ) { 812end_of_stream = true; 813break ; 814 } 815 816if (is_special_char ) { 817replace_last (line .back ()); 818is_special_char = false; 819 } 820 821if (input_char == '\033' ) {// Escape sequence 822char32_t code = getchar32 (); 823if (code == '[' ) { 824 std::string params ; 825while (true) { 826code = getchar32 (); 827if ((code >='A' && code <='Z' )|| (code >='a' && code <='z' )|| code == '~' || code == (char32_t )WEOF ) { 828break ; 829 } 830params .push_back (static_cast < char > (code )); 831 } 832 833const bool ctrl_modifier = has_ctrl_modifier (params ); 834 835if (code == 'D' ) {// left 836if (ctrl_modifier ) { 837move_word_left (char_pos ,byte_pos ,widths ,line ); 838 }else if (char_pos > 0 ) { 839int w = widths [char_pos - 1 ]; 840move_cursor (- w ); 841char_pos -- ; 842byte_pos = prev_utf8_char_pos (line ,byte_pos ); 843 } 844 }else if (code == 'C' ) {// right 845if (ctrl_modifier ) { 846move_word_right (char_pos ,byte_pos ,widths ,line ); 847 }else if (char_pos < widths .size ()) { 848int w = widths [char_pos ]; 849move_cursor (w ); 850char_pos ++ ; 851byte_pos = next_utf8_char_pos (line ,byte_pos ); 852 } 853 }else if (code == 'H' ) {// home 854move_to_line_start (char_pos ,byte_pos ,widths ); 855 }else if (code == 'F' ) {// end 856move_to_line_end (char_pos ,byte_pos ,widths ,line ); 857 }else if (code == 'A' || code == 'B' ) { 858// up/down 859if (code == 'A' ) { 860history_prev (); 861is_special_char = false; 862 }else if (code == 'B' ) { 863history_next (); 864is_special_char = false; 865 } 866 }else if ((code == '~' || (code >='A' && code <='Z' )|| (code >='a' && code <='z' ))&& !params .empty ()) { 867 std::string digits ; 868for (char ch :params ) { 869if (ch == ';' ) { 870break ; 871 } 872if (std::isdigit (static_cast < unsigned char > (ch ))) { 873digits .push_back (ch ); 874 } 875 } 876 877if (code == '~' ) { 878if (digits == "1" || digits == "7" ) {// home 879move_to_line_start (char_pos ,byte_pos ,widths ); 880 }else if (digits == "4" || digits == "8" ) {// end 881move_to_line_end (char_pos ,byte_pos ,widths ,line ); 882 }else if (digits == "3" ) {// delete 883delete_at_cursor (line ,widths ,char_pos ,byte_pos ); 884 } 885 } 886 } 887 }else if (code == 0x1B ) { 888// Discard the rest of the escape sequence 889while ((code = getchar32 ())!= (char32_t )WEOF ) { 890if ((code >='A' && code <='Z' )|| (code >='a' && code <='z' )|| code == '~' ) { 891break ; 892 } 893 } 894 } 895#if defined(_WIN32 ) 896} else if ( input_char == KEY_ARROW_LEFT ) { 897if ( char_pos > 0 ) { 898int w = widths [ char_pos - 1 ]; 899move_cursor ( - w ); 900char_pos -- ; 901byte_pos = prev_utf8_char_pos ( line , byte_pos ); 902} 903} else if ( input_char == KEY_ARROW_RIGHT ) { 904if ( char_pos < widths . size ()) { 905int w = widths [ char_pos ]; 906move_cursor ( w ); 907char_pos ++ ; 908byte_pos = next_utf8_char_pos ( line , byte_pos ); 909} 910} else if ( input_char == KEY_CTRL_ARROW_LEFT ) { 911move_word_left ( char_pos , byte_pos , widths , line ); 912} else if ( input_char == KEY_CTRL_ARROW_RIGHT ) { 913move_word_right ( char_pos , byte_pos , widths , line ); 914} else if ( input_char == KEY_HOME ) { 915move_to_line_start ( char_pos , byte_pos , widths ); 916} else if ( input_char == KEY_END ) { 917move_to_line_end ( char_pos , byte_pos , widths , line ); 918} else if ( input_char == KEY_DELETE ) { 919delete_at_cursor ( line , widths , char_pos , byte_pos ); 920} else if ( input_char == KEY_ARROW_UP || input_char == KEY_ARROW_DOWN ) { 921if ( input_char == KEY_ARROW_UP ) { 922history_prev (); 923is_special_char = false; 924} else if ( input_char == KEY_ARROW_DOWN ) { 925history_next (); 926is_special_char = false; 927} 928#endif 929} else if ( input_char == 0x08 || input_char == 0x7F ) { // Backspace 930if ( char_pos > 0 ) { 931int w = widths [ char_pos - 1 ]; 932move_cursor ( - w ); 933char_pos -- ; 934size_t prev_pos = prev_utf8_char_pos ( line , byte_pos ); 935size_t char_len = byte_pos - prev_pos ; 936byte_pos = prev_pos ; 937 938// remove the character 939line . erase ( byte_pos , char_len ); 940widths . erase ( widths . begin () + char_pos ); 941 942// redraw tail 943size_t p = byte_pos ; 944int tail_width = 0 ; 945for ( size_t i = char_pos ; i < widths . size (); ++ i ) { 946size_t next_p = next_utf8_char_pos ( line , p ); 947put_codepoint ( line . c_str () + p , next_p - p , widths [ i ]); 948tail_width += widths [ i ]; 949p = next_p ; 950} 951 952// clear display 953for ( int i = 0 ; i < w ; ++ i ) { 954fputc ( ' ' , out ); 955} 956move_cursor ( - ( tail_width + w )); 957} 958} else { 959// insert character 960std:: string new_char_str ; 961append_utf8 ( input_char , new_char_str ); 962int w = estimateWidth ( input_char ); 963 964if ( char_pos == widths . size ()) { 965// insert at the end 966line += new_char_str ; 967int real_w = put_codepoint ( new_char_str . c_str (), new_char_str . length (), w ); 968if ( real_w < 0 ) real_w = 0 ; 969widths . push_back ( real_w ); 970byte_pos += new_char_str . length (); 971char_pos ++ ; 972} else { 973// insert in middle 974line . insert ( byte_pos , new_char_str ); 975 976int real_w = put_codepoint ( new_char_str . c_str (), new_char_str . length (), w ); 977if ( real_w < 0 ) real_w = 0 ; 978 979widths . insert ( widths . begin () + char_pos , real_w ); 980 981// print the tail 982size_t p = byte_pos + new_char_str . length (); 983int tail_width = 0 ; 984for ( size_t i = char_pos + 1 ; i < widths . size (); ++ i ) { 985size_t next_p = next_utf8_char_pos ( line , p ); 986put_codepoint ( line . c_str () + p , next_p - p , widths [ i ]); 987tail_width += widths [ i ]; 988p = next_p ; 989} 990 991move_cursor ( - tail_width ); 992 993byte_pos += new_char_str . length (); 994char_pos ++ ; 995} 996} 997 998if (! line . empty () && ( line . back () == '\\' || line . back () == '/' )) { 999replace_last ( line . back ()); 1000is_special_char = true; 1001} 1002} 1003 1004bool has_more = multiline_input ; 1005if ( is_special_char ) { 1006replace_last ( ' ' ); 1007pop_cursor (); 1008 1009char last = line . back (); 1010line . pop_back (); 1011if ( last == '\\' ) { 1012line += '\n' ; 1013fputc ( '\n' , out ); 1014has_more = ! has_more ; 1015} else { 1016// llama will just eat the single space, it won't act as a space 1017if ( line . length () == 1 && line . back () == ' ' ) { 1018line . clear (); 1019pop_cursor (); 1020} 1021has_more = false; 1022} 1023} else { 1024if ( end_of_stream ) { 1025has_more = false; 1026} else { 1027line += '\n' ; 1028fputc ( '\n' , out ); 1029} 1030} 1031 1032if (! end_of_stream && ! line . empty ()) { 1033// remove the trailing newline for history storage 1034std:: string_view hline = line ; 1035if (! line . empty () && line . back () == '\n' ) { 1036hline . remove_suffix ( 1 ); 1037} 1038// TODO: maybe support multiline history entries? 1039history . add ( hline ); 1040} 1041 1042fflush ( out ); 1043return has_more ; 1044} 1045 1046static bool readline_simple (std:: string & line , bool multiline_input ) { 1047#if defined( _WIN32 ) 1048std:: wstring wline ; 1049if (!std:: getline (std:: wcin , wline )) { 1050// Input stream is bad or EOF received 1051line . clear (); 1052GenerateConsoleCtrlEvent ( CTRL_C_EVENT , 0 ); 1053return false; 1054} 1055 1056int size_needed = WideCharToMultiByte ( CP_UTF8 , 0 , & wline [ 0 ], ( int ) wline . size (), NULL , 0 , NULL , NULL ); 1057line . resize ( size_needed ); 1058WideCharToMultiByte ( CP_UTF8 , 0 , & wline [ 0 ], ( int ) wline . size (), & line [ 0 ], size_needed , NULL , NULL ); 1059#else 1060if (!std:: getline (std:: cin , line )) { 1061// Input stream is bad or EOF received 1062line . clear (); 1063return false; 1064} 1065#endif 1066if (! line . empty ()) { 1067char last = line . back (); 1068if ( last == '/' ) { // Always return control on '/' symbol 1069line . pop_back (); 1070return false; 1071} 1072if ( last == '\\' ) { // '\\' changes the default action 1073line . pop_back (); 1074multiline_input = ! multiline_input ; 1075} 1076} 1077line += '\n' ; 1078 1079// By default, continue input if multiline_input is set 1080return multiline_input ; 1081} 1082 1083bool readline (std:: string & line , bool multiline_input ) { 1084if ( simple_io ) { 1085return readline_simple ( line , multiline_input ); 1086} 1087return readline_advanced ( line , multiline_input ); 1088} 1089 1090void set_completion_callback ( completion_callback cb ) { 1091completion_cb = cb ; 1092} 1093 1094namespace spinner { 1095static const char LOADING_CHARS [] = { '|' , '/' , '-' , '\\' }; 1096static std:: condition_variable cv_stop ; 1097static std:: thread th ; 1098static size_t frame = 0 ; // only modified by one thread 1099static bool running = false; 1100static std:: mutex mtx ; 1101static auto wait_time = std::chrono:: milliseconds ( 100 ); 1102static void draw_next_frame () { 1103// don't need lock because only one thread modifies running 1104frame = ( frame + 1 ) % sizeof( LOADING_CHARS ); 1105replace_last ( LOADING_CHARS [ frame ]); 1106fflush ( out ); 1107} 1108void start () { 1109std:: unique_lock < std:: mutex > lock ( mtx ); 1110if ( simple_io || running ) { 1111return; 1112} 1113common_log_flush ( common_log_main ()); 1114fprintf ( out , "%c" , LOADING_CHARS [ 0 ]); 1115fflush ( out ); 1116frame = 1 ; 1117running = true; 1118th = std:: thread ([]() { 1119std:: unique_lock < std:: mutex > lock ( mtx ); 1120while (true) { 1121if ( cv_stop . wait_for ( lock , wait_time , []{ return ! running ; })) { 1122break; 1123} 1124draw_next_frame (); 1125} 1126}); 1127} 1128void stop () { 1129{ 1130std:: unique_lock < std:: mutex > lock ( mtx ); 1131if ( simple_io || ! running ) { 1132return; 1133} 1134running = false; 1135cv_stop . notify_all (); 1136} 1137if ( th . joinable ()) { 1138th . join (); 1139} 1140replace_last ( ' ' ); 1141pop_cursor (); 1142fflush ( out ); 1143} 1144} 1145 1146void log (const char * fmt , ...) { 1147va_list args ; 1148va_start ( args , fmt ); 1149vfprintf ( out , fmt , args ); 1150va_end ( args ); 1151} 1152 1153void error (const char * fmt , ...) { 1154va_list args ; 1155va_start ( args , fmt ); 1156display_type cur = current_display ; 1157set_display ( DISPLAY_TYPE_ERROR ); 1158vfprintf ( out , fmt , args ); 1159set_display ( cur ); // restore previous color 1160va_end ( args ); 1161} 1162 1163void flush () { 1164fflush ( out ); 1165 } 1166}