LCOV - code coverage report
Current view: top level - libemail/src/core - input_line.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 12.9 % 132 17
Test Date: 2026-04-15 21:12:52 Functions: 8.3 % 12 1

            Line data    Source code
       1              : #include "input_line.h"
       2              : #include "platform/terminal.h"
       3              : #include <stdio.h>
       4              : #include <string.h>
       5              : #include <stdint.h>
       6              : 
       7              : /* ── UTF-8 helpers ───────────────────────────────────────────────────── */
       8              : 
       9              : /** True if byte is a UTF-8 continuation byte (10xxxxxx). */
      10            0 : static int is_cont(unsigned char b) { return (b & 0xC0) == 0x80; }
      11              : 
      12              : /** Byte length of the UTF-8 code point starting at s. */
      13            0 : static size_t cp_len(const char *s) {
      14            0 :     unsigned char b = (unsigned char)*s;
      15            0 :     if (b < 0x80) return 1;
      16            0 :     if ((b & 0xE0) == 0xC0) return 2;
      17            0 :     if ((b & 0xF0) == 0xE0) return 3;
      18            0 :     return 4;
      19              : }
      20              : 
      21              : /**
      22              :  * Display column count for the first `bytes` bytes of s.
      23              :  * Uses terminal_wcwidth() for non-ASCII code points;
      24              :  * falls back to 1 column per code point if wcwidth returns <=0.
      25              :  */
      26            0 : static int display_cols(const char *s, size_t bytes) {
      27            0 :     int cols = 0;
      28            0 :     size_t i = 0;
      29            0 :     while (i < bytes) {
      30            0 :         unsigned char b = (unsigned char)s[i];
      31            0 :         if (b < 0x80) {
      32            0 :             cols++;
      33            0 :             i++;
      34              :         } else {
      35              :             /* Decode code point */
      36            0 :             uint32_t cp = 0;
      37            0 :             size_t clen = cp_len(s + i);
      38            0 :             if (clen == 2 && i + 1 < bytes)
      39            0 :                 cp = ((uint32_t)(b & 0x1F) << 6) | ((unsigned char)s[i+1] & 0x3F);
      40            0 :             else if (clen == 3 && i + 2 < bytes)
      41            0 :                 cp = ((uint32_t)(b & 0x0F) << 12)
      42            0 :                    | ((uint32_t)((unsigned char)s[i+1] & 0x3F) << 6)
      43            0 :                    | ((unsigned char)s[i+2] & 0x3F);
      44            0 :             else if (clen == 4 && i + 3 < bytes)
      45            0 :                 cp = ((uint32_t)(b & 0x07) << 18)
      46            0 :                    | ((uint32_t)((unsigned char)s[i+1] & 0x3F) << 12)
      47            0 :                    | ((uint32_t)((unsigned char)s[i+2] & 0x3F) << 6)
      48            0 :                    | ((unsigned char)s[i+3] & 0x3F);
      49              :             else
      50            0 :                 cp = b;  /* malformed — treat as 1 col */
      51            0 :             int w = terminal_wcwidth(cp);
      52            0 :             cols += (w > 0) ? w : 1;
      53            0 :             i += (clen <= bytes - i) ? clen : 1;
      54              :         }
      55              :     }
      56            0 :     return cols;
      57              : }
      58              : 
      59              : /* ── Editing operations ──────────────────────────────────────────────── */
      60              : 
      61            0 : static void il_move_left(InputLine *il) {
      62            0 :     if (il->cur == 0) return;
      63            0 :     il->cur--;
      64            0 :     while (il->cur > 0 && is_cont((unsigned char)il->buf[il->cur]))
      65            0 :         il->cur--;
      66              : }
      67              : 
      68            0 : static void il_move_right(InputLine *il) {
      69            0 :     if (il->cur >= il->len) return;
      70            0 :     il->cur++;
      71            0 :     while (il->cur < il->len && is_cont((unsigned char)il->buf[il->cur]))
      72            0 :         il->cur++;
      73              : }
      74              : 
      75            0 : static void il_backspace(InputLine *il) {
      76            0 :     if (il->cur == 0) return;
      77            0 :     size_t end = il->cur;
      78            0 :     il_move_left(il);
      79            0 :     size_t dlen = end - il->cur;
      80            0 :     memmove(il->buf + il->cur, il->buf + end, il->len - end + 1);
      81            0 :     il->len -= dlen;
      82              : }
      83              : 
      84            0 : static void il_delete_fwd(InputLine *il) {
      85            0 :     if (il->cur >= il->len) return;
      86            0 :     size_t start = il->cur;
      87            0 :     size_t end   = start + cp_len(il->buf + start);
      88            0 :     if (end > il->len) end = il->len;
      89            0 :     memmove(il->buf + start, il->buf + end, il->len - end + 1);
      90            0 :     il->len -= end - start;
      91              : }
      92              : 
      93            0 : static void il_insert(InputLine *il, char ch) {
      94            0 :     if (il->len + 1 >= il->bufsz) return;
      95            0 :     memmove(il->buf + il->cur + 1, il->buf + il->cur, il->len - il->cur + 1);
      96            0 :     il->buf[il->cur++] = ch;
      97            0 :     il->len++;
      98              : }
      99              : 
     100              : /* ── Rendering ───────────────────────────────────────────────────────── */
     101              : 
     102              : /** Returns the 1-based cursor column for the current insertion point. */
     103            0 : static int il_cursor_col(const InputLine *il, const char *prompt) {
     104            0 :     return 3 /* indent */ + display_cols(prompt, strlen(prompt))
     105            0 :                           + display_cols(il->buf, il->cur);
     106              : }
     107              : 
     108            0 : static void il_render(const InputLine *il, int trow, const char *prompt) {
     109              :     /* Move to row, clear line, print prompt and text — do NOT position the
     110              :      * cursor yet; input_line_run does that after render_below so the cursor
     111              :      * always ends up on the input row regardless of what render_below drew. */
     112            0 :     printf("\033[%d;1H\033[2K  %s%s", trow, prompt, il->buf);
     113            0 : }
     114              : 
     115              : /* ── Public API ──────────────────────────────────────────────────────── */
     116              : 
     117           18 : void input_line_init(InputLine *il, char *buf, size_t bufsz,
     118              :                      const char *initial_text) {
     119           18 :     il->buf           = buf;
     120           18 :     il->bufsz         = bufsz;
     121           18 :     il->trow          = 0;
     122           18 :     il->render_below  = NULL;
     123           18 :     il->tab_fn        = NULL;
     124           18 :     il->shift_tab_fn  = NULL;
     125           18 :     if (initial_text) {
     126           17 :         size_t n = strlen(initial_text);
     127           17 :         if (n >= bufsz) n = bufsz - 1;
     128           17 :         memcpy(buf, initial_text, n);
     129           17 :         buf[n] = '\0';
     130           17 :         il->len = n;
     131              :     } else {
     132            1 :         buf[0] = '\0';
     133            1 :         il->len = 0;
     134              :     }
     135           18 :     il->cur = il->len;   /* cursor starts at end */
     136           18 : }
     137              : 
     138            0 : int input_line_run(InputLine *il, int trow, const char *prompt) {
     139            0 :     il->trow = trow;
     140            0 :     for (;;) {
     141            0 :         il_render(il, trow, prompt);
     142            0 :         if (il->render_below) il->render_below(il);
     143              :         /* Always reposition cursor on the input row after render_below
     144              :          * (which may have left the cursor elsewhere). */
     145            0 :         printf("\033[%d;%dH\033[?25h", trow, il_cursor_col(il, prompt));
     146            0 :         fflush(stdout);
     147              : 
     148            0 :         TermKey key = terminal_read_key();
     149            0 :         switch (key) {
     150            0 :         case TERM_KEY_ENTER:
     151            0 :             printf("\033[%d;1H\033[2K\033[?25l", trow + 1);
     152            0 :             fflush(stdout);
     153            0 :             return 1;
     154              : 
     155            0 :         case TERM_KEY_ESC:
     156              :         case TERM_KEY_QUIT:
     157            0 :             printf("\033[%d;1H\033[2K\033[?25l", trow + 1);
     158            0 :             fflush(stdout);
     159            0 :             return 0;
     160              : 
     161            0 :         case TERM_KEY_BACK:
     162            0 :             il_backspace(il);
     163            0 :             break;
     164              : 
     165            0 :         case TERM_KEY_DELETE:
     166            0 :             il_delete_fwd(il);
     167            0 :             break;
     168              : 
     169            0 :         case TERM_KEY_LEFT:
     170            0 :             il_move_left(il);
     171            0 :             break;
     172              : 
     173            0 :         case TERM_KEY_RIGHT:
     174            0 :             il_move_right(il);
     175            0 :             break;
     176              : 
     177            0 :         case TERM_KEY_HOME:
     178            0 :             il->cur = 0;
     179            0 :             break;
     180              : 
     181            0 :         case TERM_KEY_END:
     182            0 :             il->cur = il->len;
     183            0 :             break;
     184              : 
     185            0 :         case TERM_KEY_TAB:
     186            0 :             if (il->tab_fn) il->tab_fn(il);
     187            0 :             break;
     188              : 
     189            0 :         case TERM_KEY_SHIFT_TAB:
     190            0 :             if (il->shift_tab_fn) il->shift_tab_fn(il);
     191            0 :             break;
     192              : 
     193            0 :         case TERM_KEY_IGNORE: {
     194            0 :             int ch = terminal_last_printable();
     195            0 :             if (ch > 0) il_insert(il, (char)ch);
     196            0 :             break;
     197              :         }
     198              : 
     199            0 :         case TERM_KEY_NEXT_LINE:
     200              :         case TERM_KEY_PREV_LINE:
     201              :         case TERM_KEY_NEXT_PAGE:
     202              :         case TERM_KEY_PREV_PAGE:
     203            0 :             break;  /* ignored in single-line context */
     204              :         }
     205              :     }
     206              : }
        

Generated by: LCOV version 2.0-1