2020-02-05 01:23:55 +00:00
|
|
|
|
/* Copyright (C) 2020 C. 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/>.
|
|
|
|
|
*/
|
|
|
|
|
|
2020-02-05 02:27:52 +00:00
|
|
|
|
#include <assert.h>
|
|
|
|
|
#include <limits.h>
|
2020-02-09 12:09:51 +00:00
|
|
|
|
#include <stdbool.h>
|
2020-02-05 01:23:55 +00:00
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
2020-02-05 02:27:52 +00:00
|
|
|
|
#include <wchar.h>
|
2020-02-09 12:32:35 +00:00
|
|
|
|
#include <wctype.h>
|
2020-02-05 01:23:55 +00:00
|
|
|
|
|
|
|
|
|
#include "chat.h"
|
|
|
|
|
|
2020-03-23 17:45:28 +00:00
|
|
|
|
enum { Cap = 1024 };
|
2020-02-05 05:20:39 +00:00
|
|
|
|
static wchar_t buf[Cap];
|
|
|
|
|
static size_t len;
|
|
|
|
|
static size_t pos;
|
2020-02-05 02:27:52 +00:00
|
|
|
|
|
2020-02-09 06:28:24 +00:00
|
|
|
|
char *editBuffer(size_t *mbsPos) {
|
2020-02-05 02:27:52 +00:00
|
|
|
|
static char mbs[MB_LEN_MAX * Cap];
|
2020-02-09 06:28:24 +00:00
|
|
|
|
|
2020-02-05 02:27:52 +00:00
|
|
|
|
const wchar_t *ptr = buf;
|
2020-02-09 06:28:24 +00:00
|
|
|
|
size_t mbsLen = wcsnrtombs(mbs, &ptr, pos, sizeof(mbs) - 1, NULL);
|
|
|
|
|
assert(mbsLen != (size_t)-1);
|
|
|
|
|
if (mbsPos) *mbsPos = mbsLen;
|
2020-02-05 01:23:55 +00:00
|
|
|
|
|
2020-02-09 06:28:24 +00:00
|
|
|
|
ptr = &buf[pos];
|
|
|
|
|
size_t n = wcsnrtombs(
|
|
|
|
|
&mbs[mbsLen], &ptr, len - pos, sizeof(mbs) - mbsLen - 1, NULL
|
|
|
|
|
);
|
2020-02-05 02:27:52 +00:00
|
|
|
|
assert(n != (size_t)-1);
|
2020-02-09 06:28:24 +00:00
|
|
|
|
mbsLen += n;
|
|
|
|
|
|
|
|
|
|
mbs[mbsLen] = '\0';
|
2020-02-05 02:27:52 +00:00
|
|
|
|
return mbs;
|
2020-02-05 01:23:55 +00:00
|
|
|
|
}
|
2020-02-05 05:20:39 +00:00
|
|
|
|
|
2020-02-09 12:09:51 +00:00
|
|
|
|
static struct {
|
|
|
|
|
wchar_t buf[Cap];
|
|
|
|
|
size_t len;
|
|
|
|
|
} cut;
|
|
|
|
|
|
|
|
|
|
static bool reserve(size_t index, size_t count) {
|
|
|
|
|
if (len + count > Cap) return false;
|
2020-02-07 06:55:26 +00:00
|
|
|
|
memmove(&buf[index + count], &buf[index], sizeof(*buf) * (len - index));
|
|
|
|
|
len += count;
|
2020-02-09 12:09:51 +00:00
|
|
|
|
return true;
|
2020-02-07 06:55:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-14 09:04:07 +00:00
|
|
|
|
static void delete(bool copy, size_t index, size_t count) {
|
2020-02-07 06:55:26 +00:00
|
|
|
|
if (index + count > len) return;
|
2020-02-14 09:04:07 +00:00
|
|
|
|
if (copy) {
|
2020-02-09 12:09:51 +00:00
|
|
|
|
memcpy(cut.buf, &buf[index], sizeof(*buf) * count);
|
|
|
|
|
cut.len = count;
|
|
|
|
|
}
|
2020-02-07 06:55:26 +00:00
|
|
|
|
memmove(
|
|
|
|
|
&buf[index], &buf[index + count], sizeof(*buf) * (len - index - count)
|
|
|
|
|
);
|
|
|
|
|
len -= count;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-30 18:56:26 +00:00
|
|
|
|
static const struct {
|
|
|
|
|
const wchar_t *name;
|
|
|
|
|
const wchar_t *string;
|
|
|
|
|
} Macros[] = {
|
2020-03-31 14:50:42 +00:00
|
|
|
|
{ L"\\bear", L"ʕっ•ᴥ•ʔっ" },
|
2020-04-01 00:12:04 +00:00
|
|
|
|
{ L"\\blush", L"(˶′◡‵˶)" },
|
2020-03-31 14:50:42 +00:00
|
|
|
|
{ L"\\cool", L"(⌐■_■)" },
|
2020-03-30 18:56:26 +00:00
|
|
|
|
{ L"\\flip", L"(╯°□°)╯︵ ┻━┻" },
|
|
|
|
|
{ L"\\gary", L"ᕕ( ᐛ )ᕗ" },
|
2020-03-31 14:50:42 +00:00
|
|
|
|
{ L"\\hug", L"(っ・∀・)っ" },
|
2020-03-30 18:56:26 +00:00
|
|
|
|
{ L"\\lenny", L"( ͡° ͜ʖ ͡°)" },
|
|
|
|
|
{ L"\\look", L"ಠ_ಠ" },
|
|
|
|
|
{ L"\\shrug", L"¯\\_(ツ)_/¯" },
|
2020-03-31 14:50:42 +00:00
|
|
|
|
{ L"\\unflip", L"┬─┬ノ(º_ºノ)" },
|
|
|
|
|
{ L"\\wave", L"ヾ(^∇^)" },
|
2020-03-30 18:56:26 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void editCompleteAdd(void) {
|
|
|
|
|
char mbs[256];
|
|
|
|
|
for (size_t i = 0; i < ARRAY_LEN(Macros); ++i) {
|
|
|
|
|
size_t n = wcstombs(mbs, Macros[i].name, sizeof(mbs));
|
|
|
|
|
assert(n != (size_t)-1);
|
|
|
|
|
completeAdd(None, mbs, Default);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void macroExpand(void) {
|
|
|
|
|
size_t macro = pos;
|
|
|
|
|
while (macro && !iswspace(buf[macro - 1])) macro--;
|
|
|
|
|
if (macro == pos) return;
|
|
|
|
|
for (size_t i = 0; i < ARRAY_LEN(Macros); ++i) {
|
|
|
|
|
if (wcsncmp(Macros[i].name, &buf[macro], pos - macro)) continue;
|
|
|
|
|
delete(false, macro, pos - macro);
|
|
|
|
|
pos = macro;
|
|
|
|
|
size_t expand = wcslen(Macros[i].string);
|
|
|
|
|
if (reserve(macro, expand)) {
|
|
|
|
|
wcsncpy(&buf[macro], Macros[i].string, expand);
|
|
|
|
|
pos += expand;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-09 07:33:53 +00:00
|
|
|
|
static struct {
|
|
|
|
|
size_t pos;
|
|
|
|
|
size_t pre;
|
|
|
|
|
size_t len;
|
|
|
|
|
} tab;
|
|
|
|
|
|
2020-02-16 03:19:55 +00:00
|
|
|
|
static void tabComplete(uint id) {
|
2020-02-09 07:33:53 +00:00
|
|
|
|
if (!tab.len) {
|
|
|
|
|
tab.pos = pos;
|
2020-03-30 17:27:35 +00:00
|
|
|
|
while (tab.pos && !iswspace(buf[tab.pos - 1])) tab.pos--;
|
2020-02-09 07:33:53 +00:00
|
|
|
|
if (tab.pos == pos) return;
|
|
|
|
|
tab.pre = pos - tab.pos;
|
|
|
|
|
tab.len = tab.pre;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-09 19:17:04 +00:00
|
|
|
|
char mbs[MB_LEN_MAX * Cap];
|
2020-02-09 07:33:53 +00:00
|
|
|
|
const wchar_t *ptr = &buf[tab.pos];
|
|
|
|
|
size_t n = wcsnrtombs(mbs, &ptr, tab.pre, sizeof(mbs) - 1, NULL);
|
|
|
|
|
assert(n != (size_t)-1);
|
|
|
|
|
mbs[n] = '\0';
|
|
|
|
|
|
|
|
|
|
const char *comp = complete(id, mbs);
|
|
|
|
|
if (!comp) comp = complete(id, mbs);
|
|
|
|
|
if (!comp) {
|
|
|
|
|
tab.len = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-09 19:17:04 +00:00
|
|
|
|
wchar_t wcs[Cap];
|
2020-04-01 16:15:44 +00:00
|
|
|
|
n = mbstowcs(wcs, comp, Cap);
|
2020-02-09 07:33:53 +00:00
|
|
|
|
assert(n != (size_t)-1);
|
|
|
|
|
if (tab.pos + n + 2 > Cap) {
|
|
|
|
|
completeReject();
|
|
|
|
|
tab.len = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-14 09:04:07 +00:00
|
|
|
|
delete(false, tab.pos, tab.len);
|
2020-03-30 18:56:26 +00:00
|
|
|
|
tab.len = n;
|
|
|
|
|
if (wcs[0] == L'\\') {
|
|
|
|
|
reserve(tab.pos, tab.len);
|
|
|
|
|
} else if (wcs[0] != L'/' && !tab.pos) {
|
|
|
|
|
tab.len += 2;
|
2020-02-09 07:33:53 +00:00
|
|
|
|
reserve(tab.pos, tab.len);
|
|
|
|
|
buf[tab.pos + n + 0] = L':';
|
|
|
|
|
buf[tab.pos + n + 1] = L' ';
|
2020-04-01 18:40:36 +00:00
|
|
|
|
} else if (tab.pos >= 2 && buf[tab.pos - 2] == L':') {
|
2020-03-30 18:56:26 +00:00
|
|
|
|
tab.len += 2;
|
2020-02-09 07:33:53 +00:00
|
|
|
|
reserve(tab.pos, tab.len);
|
|
|
|
|
buf[tab.pos - 2] = L',';
|
|
|
|
|
buf[tab.pos + n + 0] = L':';
|
|
|
|
|
buf[tab.pos + n + 1] = L' ';
|
|
|
|
|
} else {
|
2020-03-30 18:56:26 +00:00
|
|
|
|
tab.len++;
|
2020-02-09 07:33:53 +00:00
|
|
|
|
reserve(tab.pos, tab.len);
|
|
|
|
|
buf[tab.pos + n] = L' ';
|
|
|
|
|
}
|
|
|
|
|
memcpy(&buf[tab.pos], wcs, sizeof(*wcs) * n);
|
|
|
|
|
pos = tab.pos + tab.len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void tabAccept(void) {
|
|
|
|
|
completeAccept();
|
|
|
|
|
tab.len = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void tabReject(void) {
|
|
|
|
|
completeReject();
|
|
|
|
|
tab.len = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-16 03:19:55 +00:00
|
|
|
|
void edit(uint id, enum Edit op, wchar_t ch) {
|
2020-02-09 07:33:53 +00:00
|
|
|
|
size_t init = pos;
|
2020-02-05 05:20:39 +00:00
|
|
|
|
switch (op) {
|
2020-02-09 08:56:18 +00:00
|
|
|
|
break; case EditHead: pos = 0;
|
|
|
|
|
break; case EditTail: pos = len;
|
|
|
|
|
break; case EditPrev: if (pos) pos--;
|
|
|
|
|
break; case EditNext: if (pos < len) pos++;
|
2020-02-09 09:20:07 +00:00
|
|
|
|
break; case EditPrevWord: {
|
|
|
|
|
if (pos) pos--;
|
2020-02-09 12:32:35 +00:00
|
|
|
|
while (pos && !iswspace(buf[pos - 1])) pos--;
|
2020-02-09 09:20:07 +00:00
|
|
|
|
}
|
|
|
|
|
break; case EditNextWord: {
|
|
|
|
|
if (pos < len) pos++;
|
2020-02-09 12:32:35 +00:00
|
|
|
|
while (pos < len && !iswspace(buf[pos])) pos++;
|
2020-02-09 09:20:07 +00:00
|
|
|
|
}
|
2020-02-09 08:56:18 +00:00
|
|
|
|
|
2020-02-14 09:04:07 +00:00
|
|
|
|
break; case EditDeleteHead: delete(true, 0, pos); pos = 0;
|
|
|
|
|
break; case EditDeleteTail: delete(true, pos, len - pos);
|
|
|
|
|
break; case EditDeletePrev: if (pos) delete(false, --pos, 1);
|
|
|
|
|
break; case EditDeleteNext: delete(false, pos, 1);
|
2020-02-09 09:32:32 +00:00
|
|
|
|
break; case EditDeletePrevWord: {
|
|
|
|
|
if (!pos) break;
|
|
|
|
|
size_t word = pos - 1;
|
2020-02-09 12:32:35 +00:00
|
|
|
|
while (word && !iswspace(buf[word - 1])) word--;
|
2020-02-14 09:04:07 +00:00
|
|
|
|
delete(true, word, pos - word);
|
2020-02-09 09:32:32 +00:00
|
|
|
|
pos = word;
|
|
|
|
|
}
|
|
|
|
|
break; case EditDeleteNextWord: {
|
|
|
|
|
if (pos == len) break;
|
|
|
|
|
size_t word = pos + 1;
|
2020-02-09 12:32:35 +00:00
|
|
|
|
while (word < len && !iswspace(buf[word])) word++;
|
2020-02-14 09:04:07 +00:00
|
|
|
|
delete(true, pos, word - pos);
|
2020-02-09 09:32:32 +00:00
|
|
|
|
}
|
2020-02-09 12:09:51 +00:00
|
|
|
|
break; case EditPaste: {
|
|
|
|
|
if (reserve(pos, cut.len)) {
|
|
|
|
|
memcpy(&buf[pos], cut.buf, sizeof(*buf) * cut.len);
|
|
|
|
|
pos += cut.len;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-07 06:55:26 +00:00
|
|
|
|
|
2020-02-12 06:16:40 +00:00
|
|
|
|
break; case EditTranspose: {
|
|
|
|
|
if (!pos || len < 2) break;
|
|
|
|
|
if (pos == len) pos--;
|
2020-02-12 06:19:07 +00:00
|
|
|
|
wchar_t t = buf[pos - 1];
|
|
|
|
|
buf[pos - 1] = buf[pos];
|
|
|
|
|
buf[pos++] = t;
|
2020-02-12 06:16:40 +00:00
|
|
|
|
}
|
2020-04-07 14:48:44 +00:00
|
|
|
|
break; case EditCollapse: {
|
|
|
|
|
size_t ws;
|
|
|
|
|
for (pos = 0; pos < len;) {
|
|
|
|
|
for (; pos < len && !iswspace(buf[pos]); ++pos);
|
|
|
|
|
for (ws = pos; ws < len && iswspace(buf[ws]); ++ws);
|
|
|
|
|
if (pos && ws < len) {
|
|
|
|
|
delete(false, pos, ws - pos - 1);
|
|
|
|
|
buf[pos++] = L' ';
|
|
|
|
|
} else {
|
|
|
|
|
delete(false, pos, ws - pos);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-12 06:16:40 +00:00
|
|
|
|
|
2020-02-05 05:20:39 +00:00
|
|
|
|
break; case EditInsert: {
|
2020-02-09 12:09:51 +00:00
|
|
|
|
if (reserve(pos, 1)) {
|
|
|
|
|
buf[pos++] = ch;
|
|
|
|
|
}
|
2020-02-05 05:20:39 +00:00
|
|
|
|
}
|
2020-02-08 02:30:25 +00:00
|
|
|
|
break; case EditComplete: {
|
2020-02-09 07:33:53 +00:00
|
|
|
|
tabComplete(id);
|
|
|
|
|
return;
|
2020-02-08 02:30:25 +00:00
|
|
|
|
}
|
2020-03-30 18:56:26 +00:00
|
|
|
|
break; case EditExpand: {
|
|
|
|
|
macroExpand();
|
|
|
|
|
tabAccept();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-02-05 05:20:39 +00:00
|
|
|
|
break; case EditEnter: {
|
2020-02-09 07:33:53 +00:00
|
|
|
|
tabAccept();
|
2020-02-09 06:28:24 +00:00
|
|
|
|
command(id, editBuffer(NULL));
|
2020-02-09 07:33:53 +00:00
|
|
|
|
len = pos = 0;
|
|
|
|
|
return;
|
2020-02-05 05:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-09 07:33:53 +00:00
|
|
|
|
|
|
|
|
|
if (pos < init) {
|
|
|
|
|
tabReject();
|
|
|
|
|
} else {
|
|
|
|
|
tabAccept();
|
|
|
|
|
}
|
2020-02-05 05:20:39 +00:00
|
|
|
|
}
|