1/* Copyright (C) 2019 Sören Tempel
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <https://www.gnu.org/licenses/>.
15 */
16
17#include <assert.h>
18#include <err.h>
19#include <histedit.h>
20#include <stdlib.h>
21#include <string.h>
22#include <unistd.h>
23#include <wchar.h>
24
25#include <sys/types.h>
26
27#include "complete.h"
28
29typedef struct _comp comp;
30
31struct _comp {
32 wchar_t *val;
33 comp *nxt;
34};
35
36static int wcomp;
37static compfn cfn;
38static comp *comps;
39
40#define cursoridx(LINEINFO) \
41 ((LINEINFO->cursor) - (LINEINFO)->buffer)
42
43static size_t
44getin(const wchar_t **dest, const LineInfoW *linfo)
45{
46 ssize_t n;
47 size_t len;
48 const wchar_t *ws;
49
50 len = linfo->lastchar - linfo->buffer;
51 if (!wcomp) {
52 *dest = linfo->buffer;
53 return len;
54 }
55
56 n = cursoridx(linfo);
57 while (n-- && !iswspace(linfo->buffer[n]))
58 ;
59 ws = (n >= 0) ? &linfo->buffer[++n] : linfo->buffer;
60
61 *dest = ws;
62 return linfo->lastchar - ws;
63}
64
65static comp *
66nxtcomp(comp *lcomp)
67{
68 comp *nxt;
69
70 assert(lcomp);
71
72 nxt = lcomp->nxt;
73 if (!nxt && comps != lcomp)
74 nxt = comps;
75
76 return nxt; /* NULL if there is no new completion */
77}
78
79static void
80freecomps(void)
81{
82 comp *c, *n;
83
84 c = comps;
85 while (c) {
86 n = c->nxt;
87 free(c->val);
88 free(c);
89 c = n;
90 }
91
92 comps = NULL;
93}
94
95static void
96callcomp(const wchar_t *input, size_t len)
97{
98 char *mbs;
99 size_t mbslen;
100
101 if ((mbslen = wcsnrtombs(NULL, &input, len, 0, NULL)) == (size_t)-1)
102 err(EXIT_FAILURE, "wcsnrtombs failed");
103
104 if (!(mbs = calloc(mbslen + 1, sizeof(wchar_t))))
105 err(EXIT_FAILURE, "calloc failed");
106 if (wcsnrtombs(mbs, &input, len, mbslen + 1, NULL) == (size_t)-1)
107 err(EXIT_FAILURE, "wcsnrtombs failed");
108
109 cfn(mbs, mbslen);
110}
111
112void
113addcomp(char *str)
114{
115 comp *c;
116 size_t mbslen;
117
118 if ((mbslen = mbstowcs(NULL, str, 0)) == (size_t)-1)
119 err(EXIT_FAILURE, "mbstowcs failed");
120
121 if (!(c = malloc(sizeof(*c))))
122 err(EXIT_FAILURE, "malloc failed");
123 if (!(c->val = calloc(mbslen + 1, sizeof(wchar_t))))
124 err(EXIT_FAILURE, "calloc failed");
125
126 if (mbstowcs(c->val, str, mbslen + 1) == (size_t)-1)
127 err(EXIT_FAILURE, "mbstowcs failed");
128
129 if (!comps) {
130 comps = c;
131 comps->nxt = NULL;
132 } else {
133 c->nxt = comps->nxt;
134 comps->nxt = c;
135 }
136}
137
138void
139initcomp(compfn fn, int wordcomp)
140{
141 cfn = fn;
142 wcomp = wordcomp;
143}
144
145unsigned char
146complete(EditLine *el, int ch)
147{
148 comp *c;
149 size_t inlen, cidx, ccur;
150 const LineInfoW *linfo;
151 const wchar_t *input;
152 static wchar_t *linput;
153 static comp *lcomp;
154 static size_t bcur;
155
156 (void)ch;
157
158 linfo = el_wline(el);
159 inlen = getin(&input, linfo);
160
161 if (!linput || inlen != wcslen(linput) || wcsncmp(input, linput, inlen)) {
162 freecomps();
163 lcomp = NULL;
164
165 callcomp(input, inlen);
166 if (!comps)
167 return CC_ERROR;
168 c = comps;
169
170 bcur = cursoridx(linfo); /* base char cursor */
171 } else {
172 ccur = cursoridx(linfo); /* current char cursor */
173 assert(ccur >= bcur);
174 el_deletestr(el, ccur - bcur);
175
176 if (!(c = nxtcomp(lcomp)))
177 return CC_REFRESH; /* deletestr changed line */
178 }
179
180 cidx = bcur;
181 if (wcomp) {
182 assert(input >= linfo->buffer);
183 cidx -= input - linfo->buffer;
184 }
185
186 if (cidx >= wcslen(c->val))
187 goto next;
188 if (el_winsertstr(el, &c->val[cidx]) == -1)
189 errx(EXIT_FAILURE, "el_winsertstr failed");
190
191next:
192 inlen = getin(&input, el_wline(el));
193
194 free(linput);
195 if (!(linput = calloc(inlen + 1, sizeof(wchar_t))))
196 err(EXIT_FAILURE, "calloc failed");
197 memcpy(linput, input, inlen * sizeof(wchar_t));
198 lcomp = c;
199
200 return CC_REFRESH;
201}