editline-tabcomp

Tab completion implementation for BSD editline (libedit)

git clone https://git.8pit.net/editline-tabcomp.git

  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}