196 lines
4.5 KiB
C
196 lines
4.5 KiB
C
/* Copyright (C) 2018 Curtis 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
|
|
* 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.
|
|
*
|
|
* 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/>.
|
|
*/
|
|
|
|
#include <err.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <sysexits.h>
|
|
#include <wchar.h>
|
|
#include <wctype.h>
|
|
|
|
#include "chat.h"
|
|
|
|
enum { BUF_LEN = 512 };
|
|
static struct {
|
|
wchar_t buf[BUF_LEN];
|
|
wchar_t *ptr;
|
|
wchar_t *end;
|
|
wchar_t *tab;
|
|
} line = {
|
|
.ptr = line.buf,
|
|
.end = line.buf,
|
|
};
|
|
|
|
// XXX: editTail must always be called after editHead.
|
|
static wchar_t tail;
|
|
const wchar_t *editHead(void) {
|
|
tail = *line.ptr;
|
|
*line.ptr = L'\0';
|
|
return line.buf;
|
|
}
|
|
const wchar_t *editTail(void) {
|
|
if (tail) *line.ptr = tail;
|
|
*line.end = L'\0';
|
|
tail = L'\0';
|
|
return line.ptr;
|
|
}
|
|
|
|
static void left(void) {
|
|
if (line.ptr > line.buf) line.ptr--;
|
|
}
|
|
static void right(void) {
|
|
if (line.ptr < line.end) line.ptr++;
|
|
}
|
|
|
|
static void backWord(void) {
|
|
left();
|
|
editHead();
|
|
wchar_t *word = wcsrchr(line.buf, ' ');
|
|
editTail();
|
|
line.ptr = (word ? &word[1] : line.buf);
|
|
}
|
|
static void foreWord(void) {
|
|
right();
|
|
editTail();
|
|
wchar_t *word = wcschr(line.ptr, ' ');
|
|
line.ptr = (word ? word : line.end);
|
|
}
|
|
|
|
static void insert(wchar_t ch) {
|
|
if (line.end == &line.buf[BUF_LEN - 1]) return;
|
|
if (line.ptr != line.end) {
|
|
wmemmove(line.ptr + 1, line.ptr, line.end - line.ptr);
|
|
}
|
|
*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);
|
|
}
|
|
line.ptr--;
|
|
line.end--;
|
|
}
|
|
static void delete(void) {
|
|
if (line.ptr == line.end) return;
|
|
right();
|
|
backspace();
|
|
}
|
|
|
|
static void killBackWord(void) {
|
|
wchar_t *from = line.ptr;
|
|
backWord();
|
|
wmemmove(line.ptr, from, line.end - from);
|
|
line.end -= from - line.ptr;
|
|
}
|
|
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 char *prefix;
|
|
static void complete(struct Tag tag) {
|
|
if (!line.tab) {
|
|
editHead();
|
|
line.tab = wcsrchr(line.buf, L' ');
|
|
line.tab = (line.tab ? &line.tab[1] : line.buf);
|
|
prefix = awcstombs(line.tab);
|
|
if (!prefix) err(EX_DATAERR, "awcstombs");
|
|
editTail();
|
|
}
|
|
|
|
const char *next = tabNext(tag, prefix);
|
|
if (!next) return;
|
|
|
|
wchar_t *wcs = ambstowcs(next);
|
|
if (!wcs) err(EX_DATAERR, "ambstowcs");
|
|
|
|
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',') {
|
|
line.buf[pos - 2] = L',';
|
|
insert(L':');
|
|
}
|
|
}
|
|
insert(L' ');
|
|
}
|
|
|
|
static void accept(void) {
|
|
if (!line.tab) return;
|
|
line.tab = NULL;
|
|
free(prefix);
|
|
tabAccept();
|
|
}
|
|
static void reject(void) {
|
|
if (!line.tab) return;
|
|
line.tab = NULL;
|
|
free(prefix);
|
|
tabReject();
|
|
}
|
|
|
|
static void enter(struct Tag tag) {
|
|
if (line.end == line.buf) return;
|
|
editTail();
|
|
char *str = awcstombs(line.buf);
|
|
if (!str) err(EX_DATAERR, "awcstombs");
|
|
input(tag, str);
|
|
free(str);
|
|
line.ptr = line.buf;
|
|
line.end = line.buf;
|
|
}
|
|
|
|
void edit(struct Tag tag, enum Edit op, wchar_t ch) {
|
|
switch (op) {
|
|
break; case EDIT_LEFT: reject(); left();
|
|
break; case EDIT_RIGHT: reject(); right();
|
|
break; case EDIT_HOME: reject(); line.ptr = line.buf;
|
|
break; case EDIT_END: reject(); line.ptr = line.end;
|
|
|
|
break; case EDIT_BACK_WORD: reject(); backWord();
|
|
break; case EDIT_FORE_WORD: reject(); foreWord();
|
|
|
|
break; case EDIT_INSERT: accept(); insert(ch);
|
|
break; case EDIT_BACKSPACE: reject(); backspace();
|
|
break; case EDIT_DELETE: reject(); delete();
|
|
|
|
break; case EDIT_KILL_BACK_WORD: reject(); killBackWord();
|
|
break; case EDIT_KILL_FORE_WORD: reject(); killForeWord();
|
|
break; case EDIT_KILL_LINE: reject(); line.end = line.ptr;
|
|
|
|
break; case EDIT_COMPLETE: complete(tag);
|
|
|
|
break; case EDIT_ENTER: accept(); enter(tag);
|
|
}
|
|
}
|