295 lines
7.7 KiB
C
295 lines
7.7 KiB
C
/* Copyright (C) 2020, 2022 June McEnroe <june@causal.agency>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* If you modify this Program, or any covered work, by linking or
|
|
* combining it with OpenSSL (or a modified version of that library),
|
|
* containing parts covered by the terms of the OpenSSL License and the
|
|
* original SSLeay license, the licensors of this Program grant you
|
|
* additional permission to convey the resulting work. Corresponding
|
|
* Source for a non-source form of such a combination shall include the
|
|
* source code for the parts of OpenSSL used as well as that of the
|
|
* covered work.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <wchar.h>
|
|
#include <wctype.h>
|
|
|
|
#include "edit.h"
|
|
|
|
static bool isword(wchar_t ch) {
|
|
return !iswspace(ch) && !iswpunct(ch);
|
|
}
|
|
|
|
void editFree(struct Edit *e) {
|
|
free(e->buf);
|
|
free(e->cut.buf);
|
|
free(e->mbs.buf);
|
|
e->pos = e->len = e->cap = 0;
|
|
e->cut.len = 0;
|
|
e->mbs.pos = e->mbs.len = 0;
|
|
}
|
|
|
|
char *editString(struct Edit *e) {
|
|
size_t cap = e->len * MB_CUR_MAX + 1;
|
|
char *buf = realloc(e->mbs.buf, cap);
|
|
if (!buf) return NULL;
|
|
e->mbs.buf = buf;
|
|
|
|
const wchar_t *ptr = e->buf;
|
|
e->mbs.len = wcsnrtombs(e->mbs.buf, &ptr, e->pos, cap-1, NULL);
|
|
if (e->mbs.len == (size_t)-1) return NULL;
|
|
e->mbs.pos = e->mbs.len;
|
|
|
|
ptr = &e->buf[e->pos];
|
|
size_t n = wcsnrtombs(
|
|
&e->mbs.buf[e->mbs.len], &ptr, e->len - e->pos,
|
|
cap-1 - e->mbs.len, NULL
|
|
);
|
|
if (n == (size_t)-1) return NULL;
|
|
e->mbs.len += n;
|
|
|
|
e->mbs.buf[e->mbs.len] = '\0';
|
|
return e->mbs.buf;
|
|
}
|
|
|
|
int editReserve(struct Edit *e, size_t index, size_t count) {
|
|
if (index > e->len) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
if (e->len + count > e->cap) {
|
|
size_t cap = (e->cap ? e->cap * 2 : 256);
|
|
wchar_t *buf = realloc(e->buf, sizeof(*buf) * cap);
|
|
if (!buf) return -1;
|
|
e->buf = buf;
|
|
e->cap = cap;
|
|
}
|
|
wmemmove(&e->buf[index + count], &e->buf[index], e->len - index);
|
|
e->len += count;
|
|
return 0;
|
|
}
|
|
|
|
int editCopy(struct Edit *e, size_t index, size_t count) {
|
|
if (index + count > e->len) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
wchar_t *buf = realloc(e->cut.buf, sizeof(*buf) * count);
|
|
if (!buf) return -1;
|
|
e->cut.buf = buf;
|
|
wmemcpy(e->cut.buf, &e->buf[index], count);
|
|
e->cut.len = count;
|
|
return 0;
|
|
}
|
|
|
|
int editDelete(struct Edit *e, bool cut, size_t index, size_t count) {
|
|
if (index + count > e->len) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
if (cut && editCopy(e, index, count) < 0) return -1;
|
|
wmemmove(&e->buf[index], &e->buf[index + count], e->len - index - count);
|
|
e->len -= count;
|
|
if (e->pos > e->len) e->pos = e->len;
|
|
return 0;
|
|
}
|
|
|
|
int editFn(struct Edit *e, enum EditFn fn) {
|
|
int ret = 0;
|
|
switch (fn) {
|
|
break; case EditHead: e->pos = 0;
|
|
break; case EditTail: e->pos = e->len;
|
|
break; case EditPrev: if (e->pos) e->pos--;
|
|
break; case EditNext: if (e->pos < e->len) e->pos++;
|
|
break; case EditPrevWord: {
|
|
while (e->pos && !isword(e->buf[e->pos-1])) e->pos--;
|
|
while (e->pos && isword(e->buf[e->pos-1])) e->pos--;
|
|
}
|
|
break; case EditNextWord: {
|
|
while (e->pos < e->len && isword(e->buf[e->pos])) e->pos++;
|
|
while (e->pos < e->len && !isword(e->buf[e->pos])) e->pos++;
|
|
}
|
|
|
|
break; case EditDeleteHead: {
|
|
ret = editDelete(e, true, 0, e->pos);
|
|
e->pos = 0;
|
|
}
|
|
break; case EditDeleteTail: {
|
|
ret = editDelete(e, true, e->pos, e->len - e->pos);
|
|
}
|
|
break; case EditDeletePrev: {
|
|
if (e->pos) editDelete(e, false, --e->pos, 1);
|
|
}
|
|
break; case EditDeleteNext: {
|
|
editDelete(e, false, e->pos, 1);
|
|
}
|
|
break; case EditDeletePrevWord: {
|
|
if (!e->pos) break;
|
|
size_t word = e->pos;
|
|
while (word && !isword(e->buf[word-1])) word--;
|
|
while (word && isword(e->buf[word-1])) word--;
|
|
ret = editDelete(e, true, word, e->pos - word);
|
|
e->pos = word;
|
|
}
|
|
break; case EditDeleteNextWord: {
|
|
if (e->pos == e->len) break;
|
|
size_t word = e->pos;
|
|
while (word < e->len && !isword(e->buf[word])) word++;
|
|
while (word < e->len && isword(e->buf[word])) word++;
|
|
ret = editDelete(e, true, e->pos, word - e->pos);
|
|
}
|
|
|
|
break; case EditPaste: {
|
|
ret = editReserve(e, e->pos, e->cut.len);
|
|
if (ret == 0) {
|
|
wmemcpy(&e->buf[e->pos], e->cut.buf, e->cut.len);
|
|
e->pos += e->cut.len;
|
|
}
|
|
}
|
|
break; case EditTranspose: {
|
|
if (e->len < 2) break;
|
|
if (!e->pos) e->pos++;
|
|
if (e->pos == e->len) e->pos--;
|
|
wchar_t x = e->buf[e->pos-1];
|
|
e->buf[e->pos-1] = e->buf[e->pos];
|
|
e->buf[e->pos++] = x;
|
|
}
|
|
break; case EditCollapse: {
|
|
size_t ws;
|
|
for (e->pos = 0; e->pos < e->len;) {
|
|
for (; e->pos < e->len && !iswspace(e->buf[e->pos]); ++e->pos);
|
|
for (ws = e->pos; ws < e->len && iswspace(e->buf[ws]); ++ws);
|
|
if (e->pos && ws < e->len) {
|
|
editDelete(e, false, e->pos, ws - e->pos - 1);
|
|
e->buf[e->pos++] = L' ';
|
|
} else {
|
|
editDelete(e, false, e->pos, ws - e->pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
break; case EditClear: e->len = e->pos = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int editInsert(struct Edit *e, wchar_t ch) {
|
|
char mb[MB_LEN_MAX];
|
|
if (wctomb(mb, ch) < 0) return -1;
|
|
if (editReserve(e, e->pos, 1) < 0) return -1;
|
|
e->buf[e->pos++] = ch;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef TEST
|
|
#undef NDEBUG
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
static void fix(struct Edit *e, const char *str) {
|
|
editFn(e, EditClear);
|
|
for (const char *ch = str; *ch; ++ch) {
|
|
editInsert(e, (wchar_t)*ch);
|
|
}
|
|
}
|
|
|
|
static bool eq(struct Edit *e, const char *str1) {
|
|
const char *str2 = &str1[strlen(str1) + 1];
|
|
const char *buf = editString(e);
|
|
return e->mbs.pos == strlen(str1)
|
|
&& !strncmp(buf, str1, e->mbs.pos)
|
|
&& !strcmp(&buf[e->mbs.pos], str2);
|
|
}
|
|
|
|
int main(void) {
|
|
struct Edit e = { .mode = EditEmacs };
|
|
|
|
fix(&e, "foo bar");
|
|
editFn(&e, EditHead);
|
|
assert(eq(&e, "\0foo bar"));
|
|
editFn(&e, EditTail);
|
|
assert(eq(&e, "foo bar\0"));
|
|
editFn(&e, EditPrev);
|
|
assert(eq(&e, "foo ba\0r"));
|
|
editFn(&e, EditNext);
|
|
assert(eq(&e, "foo bar\0"));
|
|
|
|
fix(&e, "foo, bar");
|
|
editFn(&e, EditPrevWord);
|
|
assert(eq(&e, "foo, \0bar"));
|
|
editFn(&e, EditPrevWord);
|
|
assert(eq(&e, "\0foo, bar"));
|
|
editFn(&e, EditNextWord);
|
|
assert(eq(&e, "foo, \0bar"));
|
|
editFn(&e, EditNextWord);
|
|
assert(eq(&e, "foo, bar\0"));
|
|
|
|
fix(&e, "foo bar");
|
|
editFn(&e, EditPrevWord);
|
|
editFn(&e, EditDeleteHead);
|
|
assert(eq(&e, "\0bar"));
|
|
|
|
fix(&e, "foo bar");
|
|
editFn(&e, EditPrevWord);
|
|
editFn(&e, EditDeleteTail);
|
|
assert(eq(&e, "foo \0"));
|
|
|
|
fix(&e, "foo bar");
|
|
editFn(&e, EditDeletePrev);
|
|
assert(eq(&e, "foo ba\0"));
|
|
editFn(&e, EditHead);
|
|
editFn(&e, EditDeleteNext);
|
|
assert(eq(&e, "\0oo ba"));
|
|
|
|
fix(&e, "foo, bar");
|
|
editFn(&e, EditDeletePrevWord);
|
|
assert(eq(&e, "foo, \0"));
|
|
editFn(&e, EditDeletePrevWord);
|
|
assert(eq(&e, "\0"));
|
|
|
|
fix(&e, "foo, bar");
|
|
editFn(&e, EditHead);
|
|
editFn(&e, EditDeleteNextWord);
|
|
assert(eq(&e, "\0, bar"));
|
|
editFn(&e, EditDeleteNextWord);
|
|
assert(eq(&e, "\0"));
|
|
|
|
fix(&e, "foo bar");
|
|
editFn(&e, EditDeletePrevWord);
|
|
editFn(&e, EditPaste);
|
|
assert(eq(&e, "foo bar\0"));
|
|
editFn(&e, EditPaste);
|
|
assert(eq(&e, "foo barbar\0"));
|
|
|
|
fix(&e, "bar");
|
|
editFn(&e, EditTranspose);
|
|
assert(eq(&e, "bra\0"));
|
|
editFn(&e, EditHead);
|
|
editFn(&e, EditTranspose);
|
|
assert(eq(&e, "rb\0a"));
|
|
editFn(&e, EditTranspose);
|
|
assert(eq(&e, "rab\0"));
|
|
|
|
fix(&e, " foo bar ");
|
|
editFn(&e, EditCollapse);
|
|
assert(eq(&e, "foo bar\0"));
|
|
}
|
|
|
|
#endif /* TEST */
|