LCOV - code coverage report
Current view: top level - libemail/src/core - path_complete.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 92.2 % 103 95
Test Date: 2026-05-07 15:53:08 Functions: 100.0 % 9 9

            Line data    Source code
       1              : #include "path_complete.h"
       2              : #include "platform/terminal.h"
       3              : #include "raii.h"
       4              : #include <stdio.h>
       5              : #include <stdlib.h>
       6              : #include <string.h>
       7              : #include <dirent.h>
       8              : 
       9              : /* ── Completion state ────────────────────────────────────────────────── */
      10              : 
      11              : static struct {
      12              :     char (*names)[256]; /* sorted match names (bare, no trailing '/') */
      13              :     int   count;
      14              :     int   idx;          /* currently highlighted entry */
      15              :     int   view_start;   /* first visible entry in the display row */
      16              :     char  dir[2048];    /* directory part (ends with '/') */
      17              :     char  expected[4096]; /* il->buf[0..cur] when this set was built */
      18              :     char  suffix[4096]; /* il->buf[cur..len] when this set was built */
      19              : } g_comp;
      20              : 
      21            5 : static void g_comp_free(void) {
      22            5 :     free(g_comp.names);
      23            5 :     memset(&g_comp, 0, sizeof(g_comp));
      24            5 : }
      25              : 
      26            1 : static int name_cmp(const void *a, const void *b) {
      27            1 :     return strcmp((const char *)a, (const char *)b);
      28              : }
      29              : 
      30              : /* ── Rendering ───────────────────────────────────────────────────────── */
      31              : 
      32              : /* Render the completion bar one row below the input line.
      33              :  * Does nothing when there are no completions (preserves status bar). */
      34           19 : static void render_completions(const InputLine *il) {
      35           19 :     if (g_comp.count == 0) return;
      36            3 :     int row = il->trow + 1;
      37            3 :     printf("\033[%d;1H\033[2K", row);
      38              : 
      39            3 :     int tcols = terminal_cols();
      40              : 
      41              :     /* Keep view_start <= idx */
      42            3 :     if (g_comp.idx < g_comp.view_start)
      43            0 :         g_comp.view_start = g_comp.idx;
      44              : 
      45              :     /* Advance view_start until idx fits inside the visible window */
      46            0 :     for (;;) {
      47            3 :         int pos = 2 + (g_comp.view_start > 0 ? 2 : 0);
      48            3 :         int last = g_comp.view_start - 1;
      49            9 :         for (int i = g_comp.view_start; i < g_comp.count; i++) {
      50            6 :             int w    = (int)strlen(g_comp.names[i]) + 2;
      51            6 :             int need = (i < g_comp.count - 1) ? 3 : 0;
      52            6 :             if (pos + w + need > tcols) break;
      53            6 :             last = i;
      54            6 :             pos += w;
      55              :         }
      56            3 :         if (last >= g_comp.idx) break;
      57            0 :         g_comp.view_start++;
      58              :     }
      59              : 
      60            3 :     printf("\033[%d;1H  ", row);
      61            3 :     if (g_comp.view_start > 0)
      62            0 :         printf("\033[2m< \033[0m");
      63              : 
      64            3 :     int pos = 2 + (g_comp.view_start > 0 ? 2 : 0);
      65            9 :     for (int i = g_comp.view_start; i < g_comp.count; i++) {
      66            6 :         int w = (int)strlen(g_comp.names[i]) + 2;
      67            6 :         if (pos + w + (i < g_comp.count - 1 ? 3 : 0) > tcols) {
      68            0 :             printf("...");
      69            0 :             break;
      70              :         }
      71            6 :         if (i == g_comp.idx) printf("\033[7m");
      72            6 :         printf("%s", g_comp.names[i]);
      73            6 :         if (i == g_comp.idx) printf("\033[0m");
      74            6 :         printf("  ");
      75            6 :         pos += w;
      76              :     }
      77            3 :     fflush(stdout);
      78              : }
      79              : 
      80              : /* ── Helpers ─────────────────────────────────────────────────────────── */
      81              : 
      82              : /* Apply g_comp.names[g_comp.idx]: replace il->buf[0..cur] with dir+name,
      83              :  * then reattach the stored suffix. */
      84            3 : static void apply_comp(InputLine *il) {
      85              :     char head[4096];
      86            3 :     snprintf(head, sizeof(head), "%s%s", g_comp.dir, g_comp.names[g_comp.idx]);
      87            3 :     snprintf(il->buf, il->bufsz, "%s%s", head, g_comp.suffix);
      88            3 :     il->len = strlen(il->buf);
      89            3 :     il->cur = strlen(head);
      90            3 :     snprintf(g_comp.expected, sizeof(g_comp.expected), "%s", head);
      91            3 : }
      92              : 
      93              : /* Copy il->buf[0..il->cur] into head (NUL-terminated).
      94              :  * Returns 0 on success, -1 if head would overflow. */
      95            3 : static int make_head(const InputLine *il, char *head, size_t headsz) {
      96            3 :     if (il->cur >= headsz) return -1;
      97            3 :     memcpy(head, il->buf, il->cur);
      98            3 :     head[il->cur] = '\0';
      99            3 :     return 0;
     100              : }
     101              : 
     102              : /* ── Callbacks ───────────────────────────────────────────────────────── */
     103              : 
     104            2 : static void path_tab_fn(InputLine *il) {
     105              :     char head[4096];
     106            3 :     if (make_head(il, head, sizeof(head)) != 0) return;
     107              : 
     108              :     /* Cycling: head still matches last completion → advance */
     109            2 :     if (g_comp.count > 0 && strcmp(head, g_comp.expected) == 0) {
     110            1 :         g_comp.idx = (g_comp.idx + 1) % g_comp.count;
     111            1 :         apply_comp(il);
     112            1 :         return;
     113              :     }
     114              : 
     115              :     /* Fresh scan based on head */
     116            1 :     g_comp_free();
     117            1 :     snprintf(g_comp.suffix, sizeof(g_comp.suffix), "%s", il->buf + il->cur);
     118              : 
     119              :     const char *prefix;
     120            1 :     char *slash = strrchr(head, '/');
     121            1 :     if (slash) {
     122            1 :         size_t dlen = (size_t)(slash - head + 1);
     123            1 :         if (dlen >= sizeof(g_comp.dir)) return;
     124            1 :         memcpy(g_comp.dir, head, dlen);
     125            1 :         g_comp.dir[dlen] = '\0';
     126            1 :         prefix = slash + 1;
     127              :     } else {
     128            0 :         snprintf(g_comp.dir, sizeof(g_comp.dir), "./");
     129            0 :         prefix = head;
     130              :     }
     131              : 
     132            2 :     RAII_DIR DIR *d = opendir(g_comp.dir);
     133            1 :     if (!d) return;
     134              : 
     135            1 :     int cap = 0;
     136            1 :     size_t pfxlen = strlen(prefix);
     137              :     struct dirent *ent;
     138           10 :     while ((ent = readdir(d)) != NULL) {
     139            9 :         if (ent->d_name[0] == '.' && pfxlen == 0) continue;
     140            9 :         if (pfxlen > 0 && strncmp(ent->d_name, prefix, pfxlen) != 0) continue;
     141            2 :         if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
     142            2 :         if (g_comp.count == cap) {
     143            1 :             int nc = cap ? cap * 2 : 16;
     144            1 :             char (*tmp)[256] = realloc(g_comp.names,
     145            1 :                                        (size_t)nc * sizeof(*g_comp.names));
     146            1 :             if (!tmp) { g_comp_free(); return; }
     147            1 :             g_comp.names = tmp;
     148            1 :             cap = nc;
     149              :         }
     150            2 :         snprintf(g_comp.names[g_comp.count], 256, "%s", ent->d_name);
     151            2 :         g_comp.count++;
     152              :     }
     153              : 
     154            1 :     if (g_comp.count == 0) return;
     155              : 
     156            1 :     qsort(g_comp.names, (size_t)g_comp.count,
     157              :           sizeof(g_comp.names[0]), name_cmp);
     158            1 :     g_comp.idx        = 0;
     159            1 :     g_comp.view_start = 0;
     160            1 :     apply_comp(il);
     161              : }
     162              : 
     163            1 : static void path_shift_tab_fn(InputLine *il) {
     164              :     char head[4096];
     165            1 :     if (make_head(il, head, sizeof(head)) != 0) return;
     166            1 :     if (g_comp.count == 0 || strcmp(head, g_comp.expected) != 0) return;
     167            1 :     g_comp.idx = (g_comp.idx + g_comp.count - 1) % g_comp.count;
     168            1 :     apply_comp(il);
     169              : }
     170              : 
     171              : /* ── Public API ──────────────────────────────────────────────────────── */
     172              : 
     173            5 : void path_complete_attach(InputLine *il) {
     174            5 :     il->tab_fn       = path_tab_fn;
     175            5 :     il->shift_tab_fn = path_shift_tab_fn;
     176            5 :     il->render_below = render_completions;
     177            5 : }
     178              : 
     179            4 : void path_complete_reset(void) {
     180            4 :     g_comp_free();
     181            4 : }
        

Generated by: LCOV version 2.0-1