diff options
Diffstat (limited to 'edit.c')
-rw-r--r-- | edit.c | 404 |
1 files changed, 266 insertions, 138 deletions
diff --git a/edit.c b/edit.c index c63e4a2..effb623 100644 --- a/edit.c +++ b/edit.c @@ -1,186 +1,314 @@ -/* Copyright (C) 2018 C. McEnroe <june@causal.agency> +/* 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 Affero General Public License as published by + * 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 Affero General Public License for more details. + * GNU General Public License for more details. * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * 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 <err.h> +#include <errno.h> +#include <limits.h> #include <stdbool.h> #include <stdlib.h> -#include <sysexits.h> #include <wchar.h> #include <wctype.h> -#include "chat.h" - -enum { BufLen = 512 }; -static struct { - wchar_t buf[BufLen]; - wchar_t *ptr; - wchar_t *end; - wchar_t *tab; -} line = { - .ptr = line.buf, - .end = line.buf, -}; - -const wchar_t *editHead(void) { - return line.buf; -} -const wchar_t *editTail(void) { - return line.ptr; -} +#include "edit.h" -static void left(void) { - if (line.ptr > line.buf) line.ptr--; -} -static void right(void) { - if (line.ptr < line.end) line.ptr++; +static bool isword(wchar_t ch) { + return !iswspace(ch) && !iswpunct(ch); } -static void backWord(void) { - left(); - wchar_t *word = wcsnrchr(line.buf, line.ptr - line.buf, L' '); - line.ptr = (word ? &word[1] : line.buf); -} -static void foreWord(void) { - right(); - wchar_t *word = wcsnchr(line.ptr, line.end - line.ptr, L' '); - line.ptr = (word ? word : line.end); +char *editString(const struct Edit *e, char **buf, size_t *cap, size_t *pos) { + size_t req = e->len * MB_CUR_MAX + 1; + if (req > *cap) { + char *new = realloc(*buf, req); + if (!new) return NULL; + *buf = new; + *cap = req; + } + + const wchar_t *ptr = e->buf; + size_t len = wcsnrtombs(*buf, &ptr, e->pos, *cap-1, NULL); + if (len == (size_t)-1) return NULL; + if (pos) *pos = len; + + ptr = &e->buf[e->pos]; + size_t n = wcsnrtombs( + *buf + len, &ptr, e->len - e->pos, *cap-1 - len, NULL + ); + if (n == (size_t)-1) return NULL; + len += n; + + (*buf)[len] = '\0'; + return *buf; } -static void insert(wchar_t ch) { - if (line.end == &line.buf[BufLen - 1]) return; - if (line.ptr != line.end) { - wmemmove(line.ptr + 1, line.ptr, line.end - line.ptr); +int editReserve(struct Edit *e, size_t index, size_t count) { + if (index > e->len) { + errno = EINVAL; + return -1; } - *line.ptr++ = ch; - line.end++; -} -static void backspace(void) { - if (line.ptr == line.buf) return; - if (line.ptr != line.end) { - wmemmove(line.ptr - 1, line.ptr, line.end - line.ptr); + if (e->len + count > e->cap) { + size_t cap = (e->cap ?: 256); + while (cap < e->len + count) cap *= 2; + wchar_t *buf = realloc(e->buf, sizeof(*buf) * cap); + if (!buf) return -1; + e->buf = buf; + e->cap = cap; } - line.ptr--; - line.end--; + wmemmove(&e->buf[index + count], &e->buf[index], e->len - index); + e->len += count; + return 0; } -static void delete(void) { - if (line.ptr == line.end) return; - right(); - backspace(); + +int editCopy(struct Edit *e, size_t index, size_t count) { + if (index + count > e->len) { + errno = EINVAL; + return -1; + } + if (!e->cut) return 0; + e->cut->len = 0; + if (editReserve(e->cut, 0, count) < 0) return -1; + wmemcpy(e->cut->buf, &e->buf[index], count); + return 0; } -static void killBackWord(void) { - wchar_t *from = line.ptr; - backWord(); - wmemmove(line.ptr, from, line.end - from); - line.end -= from - line.ptr; +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; } -static void killForeWord(void) { - wchar_t *from = line.ptr; - foreWord(); - wmemmove(from, line.ptr, line.end - line.ptr); - line.end -= line.ptr - from; - line.ptr = from; + +static size_t prevSpacing(const struct Edit *e, size_t pos) { + if (!pos) return 0; + do { + pos--; + } while (pos && !wcwidth(e->buf[pos])); + return pos; } -static char *prefix; -static void complete(struct Tag tag) { - if (!line.tab) { - line.tab = wcsnrchr(line.buf, line.ptr - line.buf, L' '); - line.tab = (line.tab ? &line.tab[1] : line.buf); - prefix = awcsntombs(line.tab, line.ptr - line.tab); - if (!prefix) err(EX_DATAERR, "awcstombs"); - } +static size_t nextSpacing(const struct Edit *e, size_t pos) { + if (pos == e->len) return e->len; + do { + pos++; + } while (pos < e->len && !wcwidth(e->buf[pos])); + return pos; +} - const char *next = tabNext(tag, prefix); - if (!next) return; +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: e->pos = prevSpacing(e, e->pos); + break; case EditNext: e->pos = nextSpacing(e, 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++; + } - wchar_t *wcs = ambstowcs(next); - if (!wcs) err(EX_DATAERR, "ambstowcs"); + 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: { + size_t prev = prevSpacing(e, e->pos); + editDelete(e, false, prev, e->pos - prev); + e->pos = prev; + } + break; case EditDeleteNext: { + editDelete(e, false, e->pos, nextSpacing(e, e->pos) - e->pos); + } + 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); + } - size_t i = 0; - for (; wcs[i] && line.ptr > &line.tab[i]; ++i) { - line.tab[i] = wcs[i]; - } - while (line.ptr > &line.tab[i]) { - backspace(); - } - for (; wcs[i]; ++i) { - insert(wcs[i]); - } - free(wcs); - - size_t pos = line.tab - line.buf; - if (!pos && line.tab[0] != L'/') { - insert(L':'); - } else if (pos >= 2) { - if (line.buf[pos - 2] == L':') { - line.buf[pos - 2] = L','; - insert(L':'); + break; case EditPaste: { + if (!e->cut) break; + 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; } - insert(L' '); + return ret; } -static void accept(void) { - if (!line.tab) return; - line.tab = NULL; - free(prefix); - tabAccept(); +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; } -static void reject(void) { - if (!line.tab) return; - line.tab = NULL; - free(prefix); - tabReject(); + +#ifdef TEST +#undef NDEBUG +#include <assert.h> +#include <string.h> + +static void fix(struct Edit *e, const char *str) { + assert(0 == editFn(e, EditClear)); + for (const char *ch = str; *ch; ++ch) { + assert(0 == editInsert(e, (wchar_t)*ch)); + } } -static void enter(struct Tag tag) { - if (line.end == line.buf) return; - *line.end = L'\0'; - char *str = awcstombs(line.buf); - if (!str) err(EX_DATAERR, "awcstombs"); - input(tag, str); - free(str); - line.ptr = line.buf; - line.end = line.buf; +static bool eq(struct Edit *e, const char *str1) { + size_t pos; + static size_t cap; + static char *buf; + assert(NULL != editString(e, &buf, &cap, &pos)); + const char *str2 = &str1[strlen(str1) + 1]; + return pos == strlen(str1) + && !strncmp(buf, str1, pos) + && !strcmp(&buf[pos], str2); } -void edit(struct Tag tag, enum Edit op, wchar_t ch) { - switch (op) { - break; case EditLeft: reject(); left(); - break; case EditRight: reject(); right(); - break; case EditHome: reject(); line.ptr = line.buf; - break; case EditEnd: reject(); line.ptr = line.end; +#define editFn(...) assert(0 == editFn(__VA_ARGS__)) - break; case EditBackWord: reject(); backWord(); - break; case EditForeWord: reject(); foreWord(); +int main(void) { + struct Edit cut = {0}; + struct Edit e = { .cut = &cut }; - break; case EditInsert: accept(); insert(ch); - break; case EditBackspace: reject(); backspace(); - break; case EditDelete: reject(); delete(); + 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")); - break; case EditKill: reject(); line.ptr = line.end = line.buf; - break; case EditKillBackWord: reject(); killBackWord(); - break; case EditKillForeWord: reject(); killForeWord(); - break; case EditKillEnd: reject(); line.end = line.ptr; + 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")); - break; case EditComplete: complete(tag); + fix(&e, "foo bar"); + editFn(&e, EditPrevWord); + editFn(&e, EditDeleteHead); + assert(eq(&e, "\0bar")); - break; case EditEnter: accept(); enter(tag); - } + 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")); - *line.end = L'\0'; + 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 */ |