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 37 : static void g_comp_free(void) {
22 37 : free(g_comp.names);
23 37 : memset(&g_comp, 0, sizeof(g_comp));
24 37 : }
25 :
26 126 : static int name_cmp(const void *a, const void *b) {
27 126 : 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 22 : static void render_completions(const InputLine *il) {
35 22 : if (g_comp.count == 0) return;
36 5 : int row = il->trow + 1;
37 5 : printf("\033[%d;1H\033[2K", row);
38 :
39 5 : int tcols = terminal_cols();
40 :
41 : /* Keep view_start <= idx */
42 5 : 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 20 : for (;;) {
47 25 : int pos = 2 + (g_comp.view_start > 0 ? 2 : 0);
48 25 : int last = g_comp.view_start - 1;
49 159 : for (int i = g_comp.view_start; i < g_comp.count; i++) {
50 155 : int w = (int)strlen(g_comp.names[i]) + 2;
51 155 : int need = (i < g_comp.count - 1) ? 3 : 0;
52 155 : if (pos + w + need > tcols) break;
53 134 : last = i;
54 134 : pos += w;
55 : }
56 25 : if (last >= g_comp.idx) break;
57 20 : g_comp.view_start++;
58 : }
59 :
60 5 : printf("\033[%d;1H ", row);
61 5 : if (g_comp.view_start > 0)
62 1 : printf("\033[2m< \033[0m");
63 :
64 5 : int pos = 2 + (g_comp.view_start > 0 ? 2 : 0);
65 19 : for (int i = g_comp.view_start; i < g_comp.count; i++) {
66 15 : int w = (int)strlen(g_comp.names[i]) + 2;
67 15 : if (pos + w + (i < g_comp.count - 1 ? 3 : 0) > tcols) {
68 1 : printf("...");
69 1 : break;
70 : }
71 14 : if (i == g_comp.idx) printf("\033[7m");
72 14 : printf("%s", g_comp.names[i]);
73 14 : if (i == g_comp.idx) printf("\033[0m");
74 14 : printf(" ");
75 14 : pos += w;
76 : }
77 5 : 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 49 : static void apply_comp(InputLine *il) {
85 : char head[4096];
86 49 : snprintf(head, sizeof(head), "%s%s", g_comp.dir, g_comp.names[g_comp.idx]);
87 49 : snprintf(il->buf, il->bufsz, "%s%s", head, g_comp.suffix);
88 49 : il->len = strlen(il->buf);
89 49 : il->cur = strlen(head);
90 49 : snprintf(g_comp.expected, sizeof(g_comp.expected), "%s", head);
91 49 : }
92 :
93 : /* Copy il->buf[0..il->cur] into head (NUL-terminated).
94 : * Returns 0 on success, -1 if head would overflow. */
95 52 : static int make_head(const InputLine *il, char *head, size_t headsz) {
96 52 : if (il->cur >= headsz) return -1;
97 52 : memcpy(head, il->buf, il->cur);
98 52 : head[il->cur] = '\0';
99 52 : return 0;
100 : }
101 :
102 : /* ── Callbacks ───────────────────────────────────────────────────────── */
103 :
104 47 : static void path_tab_fn(InputLine *il) {
105 : char head[4096];
106 80 : if (make_head(il, head, sizeof(head)) != 0) return;
107 :
108 : /* Cycling: head still matches last completion → advance */
109 47 : if (g_comp.count > 0 && strcmp(head, g_comp.expected) == 0) {
110 31 : g_comp.idx = (g_comp.idx + 1) % g_comp.count;
111 31 : apply_comp(il);
112 31 : return;
113 : }
114 :
115 : /* Fresh scan based on head */
116 16 : g_comp_free();
117 16 : snprintf(g_comp.suffix, sizeof(g_comp.suffix), "%s", il->buf + il->cur);
118 :
119 : const char *prefix;
120 16 : char *slash = strrchr(head, '/');
121 16 : if (slash) {
122 15 : size_t dlen = (size_t)(slash - head + 1);
123 15 : if (dlen >= sizeof(g_comp.dir)) return;
124 15 : memcpy(g_comp.dir, head, dlen);
125 15 : g_comp.dir[dlen] = '\0';
126 15 : prefix = slash + 1;
127 : } else {
128 1 : snprintf(g_comp.dir, sizeof(g_comp.dir), "./");
129 1 : prefix = head;
130 : }
131 :
132 32 : RAII_DIR DIR *d = opendir(g_comp.dir);
133 16 : if (!d) return;
134 :
135 16 : int cap = 0;
136 16 : size_t pfxlen = strlen(prefix);
137 : struct dirent *ent;
138 125 : while ((ent = readdir(d)) != NULL) {
139 109 : if (ent->d_name[0] == '.' && pfxlen == 0) continue;
140 96 : if (pfxlen > 0 && strncmp(ent->d_name, prefix, pfxlen) != 0) continue;
141 57 : if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
142 55 : if (g_comp.count == cap) {
143 15 : int nc = cap ? cap * 2 : 16;
144 15 : char (*tmp)[256] = realloc(g_comp.names,
145 15 : (size_t)nc * sizeof(*g_comp.names));
146 15 : if (!tmp) { g_comp_free(); return; }
147 15 : g_comp.names = tmp;
148 15 : cap = nc;
149 : }
150 55 : snprintf(g_comp.names[g_comp.count], 256, "%s", ent->d_name);
151 55 : g_comp.count++;
152 : }
153 :
154 16 : if (g_comp.count == 0) return;
155 :
156 14 : qsort(g_comp.names, (size_t)g_comp.count,
157 : sizeof(g_comp.names[0]), name_cmp);
158 14 : g_comp.idx = 0;
159 14 : g_comp.view_start = 0;
160 14 : apply_comp(il);
161 : }
162 :
163 5 : static void path_shift_tab_fn(InputLine *il) {
164 : char head[4096];
165 6 : if (make_head(il, head, sizeof(head)) != 0) return;
166 5 : if (g_comp.count == 0 || strcmp(head, g_comp.expected) != 0) return;
167 4 : g_comp.idx = (g_comp.idx + g_comp.count - 1) % g_comp.count;
168 4 : apply_comp(il);
169 : }
170 :
171 : /* ── Public API ──────────────────────────────────────────────────────── */
172 :
173 21 : void path_complete_attach(InputLine *il) {
174 21 : il->tab_fn = path_tab_fn;
175 21 : il->shift_tab_fn = path_shift_tab_fn;
176 21 : il->render_below = render_completions;
177 21 : }
178 :
179 21 : void path_complete_reset(void) {
180 21 : g_comp_free();
181 21 : }
|