git.strcat.st

/strcat/minitox.git/ - summarytreelogarchive

subject
fix: redraw wrapped input as multiline instead of repeating prompt
commit
f6123a399ef2a7ffe00d40a1c34337a45f0ad93e
date
2026-04-18T16:41:41Z
message
diff
 minitox.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 94 insertions(+), 5 deletions(-)

diff --git a/minitox.c b/minitox.c
index 0db5d06..963a5c3 100644
--- a/minitox.c
+++ b/minitox.c
@@ -16,6 +16,7 @@
 #include <termios.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <sys/ioctl.h>
 
 #include <tox/tox.h>
 
@@ -383,6 +384,7 @@ struct AsyncREPL {
     size_t sz;
     int  nbuf;
     int nstack;
+    int rendered_rows;
 };
 
 struct termios saved_tattr;
@@ -401,6 +403,7 @@ void setup_arepl(void) {
     async_repl = malloc(sizeof(struct AsyncREPL));
     async_repl->nbuf = 0;
     async_repl->nstack = 0;
+    async_repl->rendered_rows = 0;
     async_repl->sz = LINE_MAX_SIZE;
     async_repl->line = malloc(LINE_MAX_SIZE);
     async_repl->prompt = malloc(LINE_MAX_SIZE);
@@ -448,13 +451,95 @@ void setup_arepl(void) {
     atexit(arepl_exit);
 }
 
+static int terminal_width(void) {
+    struct winsize ws;
+    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) {
+        return ws.ws_col;
+    }
+    return 80;
+}
+
+static int prompt_visible_len(const char *s) {
+    int n = 0;
+    while (*s) {
+        if ((unsigned char)s[0] == 0x1b && s[1] == '[') {
+            s += 2;
+            while (*s && ((*s >= '0' && *s <= '9') || *s == ';' || *s == '?')) s++;
+            if (*s) s++;
+            continue;
+        }
+        n++;
+        s++;
+    }
+    return n;
+}
+
+static char arepl_line_at(const struct AsyncREPL *arepl, int idx) {
+    if (idx < arepl->nbuf) {
+        return arepl->line[idx];
+    }
+    int tail = idx - arepl->nbuf;
+    return arepl->line[arepl->sz - arepl->nstack + tail];
+}
+
+static void arepl_cursor_pos(int cols, int prompt_cols, int idx, int *row, int *col) {
+    int r = 1;
+    int c = prompt_cols;
+    if (cols <= prompt_cols) cols = prompt_cols + 1;
+
+    for (int i = 0; i < idx; i++) {
+        if (c >= cols) {
+            r++;
+            c = prompt_cols;
+        }
+        c++;
+    }
+    *row = r;
+    *col = c;
+}
+
 void arepl_reprint(struct AsyncREPL *arepl) {
-    fputs(CODE_ERASE_LINE, stdout);
+    int rows_to_clear = arepl->rendered_rows > 0 ? arepl->rendered_rows : 1;
+    int cols = terminal_width();
+    int prompt_cols = prompt_visible_len(arepl->prompt ? arepl->prompt : "");
+    int total = arepl->nbuf + arepl->nstack;
+
+    fputs("\r", stdout);
+    if (rows_to_clear > 1) {
+        printf("\033[%dA", rows_to_clear - 1);
+    }
+    for (int i = 0; i < rows_to_clear; i++) {
+        fputs("\r\033[2K", stdout);
+        if (i + 1 < rows_to_clear) fputs("\033[1B", stdout);
+    }
+    if (rows_to_clear > 1) {
+        printf("\033[%dA\r", rows_to_clear - 1);
+    }
+
     if (arepl->prompt) fputs(arepl->prompt, stdout);
-    if (arepl->nbuf > 0) printf("%.*s", arepl->nbuf, arepl->line);
+
+    int rendered_rows = 1;
+    int col = prompt_cols;
+    if (cols <= prompt_cols) cols = prompt_cols + 1;
+    for (int i = 0; i < total; i++) {
+        if (col >= cols) {
+            fputs("\n", stdout);
+            for (int j = 0; j < prompt_cols; j++) fputc(' ', stdout);
+            rendered_rows++;
+            col = prompt_cols;
+        }
+        fputc(arepl_line_at(arepl, i), stdout);
+        col++;
+    }
+    arepl->rendered_rows = rendered_rows;
+
     if (arepl->nstack > 0) {
-        printf("%.*s",(int)arepl->nstack, arepl->line + arepl->sz - arepl->nstack);
-        printf("\033[%dD",arepl->nstack); // move cursor
+        int end_row, end_col, cur_row, cur_col;
+        arepl_cursor_pos(cols, prompt_cols, total, &end_row, &end_col);
+        arepl_cursor_pos(cols, prompt_cols, arepl->nbuf, &cur_row, &cur_col);
+        if (end_row > cur_row) printf("\033[%dA", end_row - cur_row);
+        printf("\r");
+        if (cur_col > 0) printf("\033[%dC", cur_col);
     }
     fflush(stdout);
 }
@@ -1288,11 +1373,13 @@ char *poptok(char **strp) {
 void repl_iterate(void){
     static char buf[128];
     static char line[LINE_MAX_SIZE];
+    bool has_input = false;
     while (1) {
         int n = read(NEW_STDIN_FILENO, buf, sizeof(buf));
         if (n <= 0) {
             break;
         }
+        has_input = true;
         for (int i=0;i<n;i++) { // for_1
             char c = buf[i];
             if (c == '\004')          /* C-d */
@@ -1354,7 +1441,9 @@ void repl_iterate(void){
             WARN("! Invalid command, use `/help` to get list of available commands.");
         } // end for_1
     } // end while
-    arepl_reprint(async_repl);
+    if (has_input) {
+        arepl_reprint(async_repl);
+    }
 }