11#include " console.h"
22#include < vector>
33#include < iostream>
4+ #include < cassert>
45
56#if defined(_WIN32)
67#define WIN32_LEAN_AND_MEAN
@@ -336,20 +337,55 @@ namespace console {
336337 }
337338
338339 // Helper function to remove the last UTF-8 character from a string
339- static void pop_back_utf8_char (std::string & line) {
340- if (line.empty ()) {
341- return ;
340+ static size_t prev_utf8_char_pos (const std::string & line, size_t pos) {
341+ if (pos == 0 ) return 0 ;
342+ pos--;
343+ while (pos > 0 && (line[pos] & 0xC0 ) == 0x80 ) {
344+ pos--;
342345 }
346+ return pos;
347+ }
343348
344- size_t pos = line.length () - 1 ;
349+ static size_t next_utf8_char_pos (const std::string & line, size_t pos) {
350+ if (pos >= line.length ()) return line.length ();
351+ pos++;
352+ while (pos < line.length () && (line[pos] & 0xC0 ) == 0x80 ) {
353+ pos++;
354+ }
355+ return pos;
356+ }
345357
346- // Find the start of the last UTF-8 character (checking up to 4 bytes back)
347- for (size_t i = 0 ; i < 3 && pos > 0 ; ++i, --pos) {
348- if ((line[pos] & 0xC0 ) != 0x80 ) {
349- break ; // Found the start of the character
358+ static void move_cursor (int delta) {
359+ if (delta == 0 ) return ;
360+ #if defined(_WIN32)
361+ if (hConsole != NULL ) {
362+ CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
363+ GetConsoleScreenBufferInfo (hConsole, &bufferInfo);
364+ COORD newCursorPosition = bufferInfo.dwCursorPosition ;
365+ int width = bufferInfo.dwSize .X ;
366+ int newX = newCursorPosition.X + delta;
367+ int newY = newCursorPosition.Y ;
368+
369+ while (newX >= width) {
370+ newX -= width;
371+ newY++;
350372 }
373+ while (newX < 0 ) {
374+ newX += width;
375+ newY--;
376+ }
377+
378+ newCursorPosition.X = newX;
379+ newCursorPosition.Y = newY;
380+ SetConsoleCursorPosition (hConsole, newCursorPosition);
381+ }
382+ #else
383+ if (delta < 0 ) {
384+ for (int i = 0 ; i < -delta; i++) fprintf (out, " \b " );
385+ } else {
386+ for (int i = 0 ; i < delta; i++) fprintf (out, " \033 [C" );
351387 }
352- line. erase (pos);
388+ # endif
353389 }
354390
355391 static bool readline_advanced (std::string & line, bool multiline_input) {
@@ -362,8 +398,14 @@ namespace console {
362398 bool is_special_char = false ;
363399 bool end_of_stream = false ;
364400
401+ size_t byte_pos = 0 ; // current byte index
402+ size_t char_pos = 0 ; // current character index (one char can be multiple bytes)
403+
365404 char32_t input_char;
366405 while (true ) {
406+ assert (char_pos <= byte_pos);
407+ assert (char_pos <= widths.size ());
408+
367409 fflush (out); // Ensure all output is displayed before waiting for input
368410 input_char = getchar32 ();
369411
@@ -384,7 +426,35 @@ namespace console {
384426
385427 if (input_char == ' \033 ' ) { // Escape sequence
386428 char32_t code = getchar32 ();
387- if (code == ' [' || code == 0x1B ) {
429+ if (code == ' [' ) {
430+ code = getchar32 ();
431+ if (code == ' D' ) { // left
432+ if (char_pos > 0 ) {
433+ int w = widths[char_pos - 1 ];
434+ move_cursor (-w);
435+ char_pos--;
436+ byte_pos = prev_utf8_char_pos (line, byte_pos);
437+ }
438+ } else if (code == ' C' ) { // right
439+ if (char_pos < widths.size ()) {
440+ int w = widths[char_pos];
441+ move_cursor (w);
442+ char_pos++;
443+ byte_pos = next_utf8_char_pos (line, byte_pos);
444+ }
445+ } else if (code == ' A' || code == ' B' ) {
446+ // up/down
447+ // TODO: Implement history navigation
448+ } else {
449+ // Discard the rest of the escape sequence
450+ while ((code = getchar32 ()) != (char32_t ) WEOF) {
451+ if ((code >= ' A' && code <= ' Z' ) || (code >= ' a' && code <= ' z' ) || code == ' ~' ) {
452+ break ;
453+ }
454+ }
455+ }
456+ // TODO: Handle Ctrl+Arrow
457+ } else if (code == 0x1B ) {
388458 // Discard the rest of the escape sequence
389459 while ((code = getchar32 ()) != (char32_t ) WEOF) {
390460 if ((code >= ' A' && code <= ' Z' ) || (code >= ' a' && code <= ' z' ) || code == ' ~' ) {
@@ -393,27 +463,72 @@ namespace console {
393463 }
394464 }
395465 } else if (input_char == 0x08 || input_char == 0x7F ) { // Backspace
396- if (!widths.empty ()) {
397- int count;
398- do {
399- count = widths.back ();
400- widths.pop_back ();
401- // Move cursor back, print space, and move cursor back again
402- for (int i = 0 ; i < count; i++) {
403- replace_last (' ' );
404- pop_cursor ();
405- }
406- pop_back_utf8_char (line);
407- } while (count == 0 && !widths.empty ());
466+ if (char_pos > 0 ) {
467+ int w = widths[char_pos - 1 ];
468+ move_cursor (-w);
469+ char_pos--;
470+ size_t prev_pos = prev_utf8_char_pos (line, byte_pos);
471+ size_t char_len = byte_pos - prev_pos;
472+ byte_pos = prev_pos;
473+
474+ // remove the character
475+ line.erase (byte_pos, char_len);
476+ widths.erase (widths.begin () + char_pos);
477+
478+ // redraw tail
479+ size_t p = byte_pos;
480+ int tail_width = 0 ;
481+ for (size_t i = char_pos; i < widths.size (); ++i) {
482+ size_t next_p = next_utf8_char_pos (line, p);
483+ put_codepoint (line.c_str () + p, next_p - p, widths[i]);
484+ tail_width += widths[i];
485+ p = next_p;
486+ }
487+
488+ // clear display
489+ for (int i = 0 ; i < w; ++i) {
490+ fputc (' ' , out);
491+ }
492+ move_cursor (-(tail_width + w));
408493 }
409494 } else {
410- int offset = line.length ();
411- append_utf8 (input_char, line);
412- int width = put_codepoint (line.c_str () + offset, line.length () - offset, estimateWidth (input_char));
413- if (width < 0 ) {
414- width = 0 ;
495+ // insert character
496+ std::string new_char_str;
497+ append_utf8 (input_char, new_char_str);
498+ int w = estimateWidth (input_char);
499+
500+ if (char_pos == widths.size ()) {
501+ // insert at the end
502+ line += new_char_str;
503+ int real_w = put_codepoint (new_char_str.c_str (), new_char_str.length (), w);
504+ if (real_w < 0 ) real_w = 0 ;
505+ widths.push_back (real_w);
506+ byte_pos += new_char_str.length ();
507+ char_pos++;
508+ } else {
509+ // insert in middle
510+ line.insert (byte_pos, new_char_str);
511+
512+ int real_w = put_codepoint (new_char_str.c_str (), new_char_str.length (), w);
513+ if (real_w < 0 ) real_w = 0 ;
514+
515+ widths.insert (widths.begin () + char_pos, real_w);
516+
517+ // print the tail
518+ size_t p = byte_pos + new_char_str.length ();
519+ int tail_width = 0 ;
520+ for (size_t i = char_pos + 1 ; i < widths.size (); ++i) {
521+ size_t next_p = next_utf8_char_pos (line, p);
522+ put_codepoint (line.c_str () + p, next_p - p, widths[i]);
523+ tail_width += widths[i];
524+ p = next_p;
525+ }
526+
527+ move_cursor (-tail_width);
528+
529+ byte_pos += new_char_str.length ();
530+ char_pos++;
415531 }
416- widths.push_back (width);
417532 }
418533
419534 if (!line.empty () && (line.back () == ' \\ ' || line.back () == ' /' )) {
0 commit comments