remove overly generic code - iomenu - interactive terminal-based selection menu
HTML git clone git://bitreich.org/iomenu git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/iomenu
DIR Log
DIR Files
DIR Refs
DIR Tags
DIR README
DIR LICENSE
---
DIR commit 774936ea25643995d42c2ca1cd7052875fc35573
DIR parent f099742c20929af4c304bb477ad8ec42e0bb6f17
HTML Author: Josuah Demangeon <me@josuah.net>
Date: Tue, 27 Oct 2020 22:34:48 +0100
remove overly generic code
Generic library-style code is good for larger projects that have an interest
at putting in common a lot of code.
Narrowing the scope to just the problem to solve makes the implementation simpler.
Diffstat:
M Makefile | 9 ++++-----
A compat.c | 59 +++++++++++++++++++++++++++++++
R src/compat.h -> compat.h | 0
M iomenu.c | 129 +++++++++++++++++++------------
D src/compat/strcasestr.c | 25 -------------------------
D src/compat/strlcpy.c | 15 ---------------
D src/compat/strsep.c | 23 -----------------------
D src/log.c | 72 -------------------------------
D src/log.h | 14 --------------
D src/mem.c | 160 -------------------------------
D src/mem.h | 59 -------------------------------
D src/term.c | 107 -------------------------------
D src/term.h | 39 -------------------------------
A term.c | 107 +++++++++++++++++++++++++++++++
A term.h | 38 +++++++++++++++++++++++++++++++
R src/utf8.c -> utf8.c | 0
R src/utf8.h -> utf8.h | 0
R src/compat/wcwidth.c -> wcwidth.c | 0
A wcwidth.h | 13 +++++++++++++
19 files changed, 299 insertions(+), 570 deletions(-)
---
DIR diff --git a/Makefile b/Makefile
@@ -1,12 +1,11 @@
NAME = iomenu
VERSION = 0.1
-SRC = src/utf8.c src/log.c src/mem.c src/compat/strcasestr.c \
- src/compat/strsep.c src/compat/strlcpy.c src/compat/wcwidth.c src/term.c
-HDR = src/mem.h src/compat.h src/log.h src/term.h src/utf8.h
+SRC = utf8.c compat.c wcwidth.c term.c
+HDR = utf8.h compat.h wcwidth.h term.h
+OBJ = ${SRC:.c=.o}
BIN = iomenu
MAN1 = ${BIN:=.1}
-OBJ = ${SRC:.c=.o}
LIB =
W = -Wall -Wextra -std=c99 --pedantic
@@ -28,7 +27,7 @@ ${BIN}: ${OBJ} ${BIN:=.o}
${CC} ${LDFLAGS} -o $@ $@.o ${OBJ} ${LIB}
clean:
- rm -rf *.o */*.o ${BIN} ${NAME}-${VERSION} *.gz
+ rm -rf *.o ${BIN} ${NAME}-${VERSION} *.gz
install:
mkdir -p ${DESTDIR}${PREFIX}/bin
DIR diff --git a/compat.c b/compat.c
@@ -0,0 +1,59 @@
+#include <ctype.h>
+#include <stddef.h>
+
+#include <string.h>
+
+#include "compat.h"
+
+char *
+strcasestr(const char *str1, const char *str2)
+{
+ const char *s1;
+ const char *s2;
+
+ for (;;) {
+ s1 = str1;
+ s2 = str2;
+ while (*s1 != '\0' && tolower(*s1) == tolower(*s2))
+ s1++, s2++;
+ if (*s2 == '\0')
+ return (char *) str1;
+ if (*s1 == '\0')
+ return NULL;
+ str1++;
+ }
+
+ return NULL;
+}
+
+size_t
+strlcpy(char *buf, char const *str, size_t sz)
+{
+ size_t len, cpy;
+
+ len = strlen(str);
+ cpy = (len > sz) ? (sz) : (len);
+ memcpy(buf, str, cpy + 1);
+ buf[sz - 1] = '\0';
+ return len;
+}
+
+char *
+strsep(char **str_p, char const *sep)
+{
+ char *s, *prev;
+
+ if (*str_p == NULL)
+ return NULL;
+
+ for (s = prev = *str_p; strchr(sep, *s) == NULL; s++)
+ continue;
+
+ if (*s == '\0') {
+ *str_p = NULL;
+ } else {
+ *s = '\0';
+ *str_p = s + 1;
+ }
+ return prev;
+}
DIR diff --git a/src/compat.h b/compat.h
DIR diff --git a/iomenu.c b/iomenu.c
@@ -10,23 +10,21 @@
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
+#include <assert.h>
#include "compat.h"
-#include "log.h"
-#include "mem.h"
#include "term.h"
#include "utf8.h"
struct {
- FILE *tty;
char input[LINE_MAX];
size_t cur;
char **lines_buf;
- size_t lines_len;
+ size_t lines_count;
char **match_buf;
- size_t match_len;
+ size_t match_count;
} ctx;
int opt_comment;
@@ -51,20 +49,44 @@ match_line(char *line, char **tokv)
* error message.
*/
static void
-goodbye(const char *s)
+die(const char *msg)
{
int e = errno;
term_raw_off(2);
+
+ fprintf(stderr, "iomenu: ");
errno = e;
- die("%s", s);
+ perror(msg);
+
+ exit(1);
+}
+
+void *
+xrealloc(void *ptr, size_t sz)
+{
+ ptr = realloc(ptr, sz);
+ if (ptr == NULL)
+ die("realloc");
+ return ptr;
+}
+
+void *
+xmalloc(size_t sz)
+{
+ void *ptr;
+
+ ptr = malloc(sz);
+ if (ptr == NULL)
+ die("malloc");
+ return ptr;
}
static void
do_move(int sign)
{
/* integer overflow will do what we need */
- for (size_t i = ctx.cur + sign; i < ctx.match_len; i += sign) {
+ for (size_t i = ctx.cur + sign; i < ctx.match_count; i += sign) {
if (opt_comment == 0 || ctx.match_buf[i][0] != '#') {
ctx.cur = i;
break;
@@ -78,7 +100,7 @@ do_move(int sign)
* of `searchv' of size `searchc'
*/
static void
-do_filter(char **search_buf, size_t search_len)
+do_filter(char **search_buf, size_t search_count)
{
char **t, *tokv[(sizeof ctx.input + 1) * sizeof(char *)];
char *b, buf[sizeof ctx.input];
@@ -89,10 +111,10 @@ do_filter(char **search_buf, size_t search_len)
continue;
*t = NULL;
- ctx.cur = ctx.match_len = 0;
- for (size_t n = 0; n < search_len; n++)
+ ctx.cur = ctx.match_count = 0;
+ for (size_t n = 0; n < search_count; n++)
if (match_line(search_buf[n], tokv))
- ctx.match_buf[ctx.match_len++] = search_buf[n];
+ ctx.match_buf[ctx.match_count++] = search_buf[n];
if (opt_comment && ctx.match_buf[ctx.cur][0] == '#')
do_move(+1);
}
@@ -103,7 +125,7 @@ do_move_page(signed int sign)
int rows = term.winsize.ws_row - 1;
size_t i = ctx.cur - ctx.cur % rows + rows * sign;
- if (i >= ctx.match_len)
+ if (i >= ctx.match_count)
return;
ctx.cur = i - 1;
@@ -120,7 +142,7 @@ do_move_header(signed int sign)
for (ctx.cur += sign;; ctx.cur += sign) {
char *cur = ctx.match_buf[ctx.cur];
- if (ctx.cur >= ctx.match_len) {
+ if (ctx.cur >= ctx.match_count) {
ctx.cur--;
break;
}
@@ -142,7 +164,7 @@ do_remove_word(void)
len = strlen(ctx.input) - 1;
for (i = len; i >= 0 && !isspace(ctx.input[i]); i--)
ctx.input[i] = '\0';
- do_filter(ctx.lines_buf, ctx.lines_len);
+ do_filter(ctx.lines_buf, ctx.lines_count);
}
static void
@@ -157,7 +179,7 @@ do_add_char(char c)
ctx.input[len] = c;
ctx.input[len + 1] = '\0';
}
- do_filter(ctx.match_buf, ctx.match_len);
+ do_filter(ctx.match_buf, ctx.match_count);
}
static void
@@ -175,7 +197,7 @@ do_print_selection(void)
fprintf(stdout, "%c", '\t');
}
term_raw_off(2);
- if (ctx.match_len == 0
+ if (ctx.match_count == 0
|| (opt_comment && ctx.match_buf[ctx.cur][0] == '#'))
fprintf(stdout, "%s\n", ctx.input);
else
@@ -204,7 +226,7 @@ key_action(void)
return -1;
case TERM_KEY_CTRL('U'):
ctx.input[0] = '\0';
- do_filter(ctx.lines_buf, ctx.lines_len);
+ do_filter(ctx.lines_buf, ctx.lines_count);
break;
case TERM_KEY_CTRL('W'):
do_remove_word();
@@ -212,7 +234,7 @@ key_action(void)
case TERM_KEY_DELETE:
case TERM_KEY_BACKSPACE:
ctx.input[strlen(ctx.input) - 1] = '\0';
- do_filter(ctx.lines_buf, ctx.lines_len);
+ do_filter(ctx.lines_buf, ctx.lines_count);
break;
case TERM_KEY_ARROW_UP:
case TERM_KEY_CTRL('P'):
@@ -237,10 +259,10 @@ key_action(void)
do_move_page(+1);
break;
case TERM_KEY_TAB:
- if (ctx.match_len == 0)
+ if (ctx.match_count == 0)
break;
strlcpy(ctx.input, ctx.match_buf[ctx.cur], sizeof(ctx.input));
- do_filter(ctx.match_buf, ctx.match_len);
+ do_filter(ctx.match_buf, ctx.match_count);
break;
case TERM_KEY_ENTER:
case TERM_KEY_CTRL('M'):
@@ -281,7 +303,7 @@ do_print_screen(void)
i = ctx.cur - ctx.cur % rows;
m = ctx.match_buf + i;
fprintf(stderr, "\x1b[2J");
- while (p < rows && i < ctx.match_len) {
+ while (p < rows && i < ctx.match_count) {
print_line(*m, i == ctx.cur);
p++, i++, m++;
}
@@ -294,7 +316,7 @@ static void
sig_winch(int sig)
{
if (ioctl(STDERR_FILENO, TIOCGWINSZ, &term.winsize) == -1)
- goodbye("ioctl");
+ die("ioctl");
do_print_screen();
signal(sig, sig_winch);
}
@@ -306,15 +328,25 @@ usage(char const *arg0)
exit(1);
}
-static void
-read_stdin(char **buf, struct mem_pool *pool)
+static int
+read_stdin(char **buf)
{
- if (mem_read((void **)buf, pool) < 0)
- goodbye("reading standard input");
- if (memchr(*buf, '\0', mem_length(*buf)) != NULL)
- goodbye("'\\0' byte in input");
- if (mem_append((void **)buf, "", 1) < 0)
- goodbye("adding '\\0' terminator");
+ size_t len = 0;
+
+ assert(*buf == NULL);
+
+ for (int c; (c = fgetc(stdin)) != EOF;) {
+ if (c == '\0') {
+ fprintf(stderr, "iomenu: ignoring '\\0' byte in input\r\n");
+ continue;
+ }
+ *buf = xrealloc(*buf, sizeof *buf + len + 1);
+ (*buf)[len++] = c;
+ }
+ *buf = xrealloc(*buf, sizeof *buf + len + 1);
+ (*buf)[len] = '\0';
+
+ return 0;
}
/*
@@ -322,27 +354,24 @@ read_stdin(char **buf, struct mem_pool *pool)
* line, but using the input buffer and replacing '\n' by '\0'.
*/
static void
-split_lines(char *s, struct mem_pool *pool)
+split_lines(char *s)
{
- ctx.lines_buf = mem_alloc(pool, 0);
- if (ctx.lines_buf == NULL)
- goodbye("initializing full lines buffer");
+ size_t sz;
- ctx.lines_len = 0;
+ ctx.lines_count = 0;
for (;;) {
- if (mem_append((void **)&ctx.lines_buf, &s, sizeof s) < 0)
- goodbye("adding line to array");
- ctx.lines_len++;
+ sz = (ctx.lines_count + 1) * sizeof s;
+ ctx.lines_buf = xrealloc(ctx.lines_buf, sz);
+ ctx.lines_buf[ctx.lines_count++] = s;
s = strchr(s, '\n');
if (s == NULL)
break;
*s++ = '\0';
}
-
- ctx.match_buf = mem_alloc(pool, mem_length(ctx.lines_buf));
- if (ctx.match_buf == NULL)
- goodbye("initializing matching lines buffer");
+ sz = ctx.lines_count * sizeof s;
+ ctx.match_buf = xmalloc(sz);
+ memcpy(ctx.match_buf, ctx.lines_buf, sz);
}
/*
@@ -353,8 +382,7 @@ split_lines(char *s, struct mem_pool *pool)
int
main(int argc, char *argv[])
{
- struct mem_pool pool = {0};
- char *buf;
+ char *buf = NULL, *arg0;
arg0 = *argv;
for (int opt; (opt = getopt(argc, argv, "#v")) > 0;) {
@@ -372,17 +400,17 @@ main(int argc, char *argv[])
argc -= optind;
argv += optind;
- read_stdin(&buf, &pool);
- split_lines(buf, &pool);
+ read_stdin(&buf);
+ split_lines(buf);
- do_filter(ctx.lines_buf, ctx.lines_len);
+ do_filter(ctx.lines_buf, ctx.lines_count);
if (!isatty(2))
- goodbye("file descriptor 2 (stderr)");
+ die("file descriptor 2 (stderr)");
freopen("/dev/tty", "w+", stderr);
if (stderr == NULL)
- goodbye("re-opening standard error read/write");
+ die("re-opening standard error read/write");
term_raw_on(2);
sig_winch(SIGWINCH);
@@ -396,6 +424,5 @@ main(int argc, char *argv[])
term_raw_off(2);
- mem_free(&pool);
return 0;
}
DIR diff --git a/src/compat/strcasestr.c b/src/compat/strcasestr.c
@@ -1,25 +0,0 @@
-#include <ctype.h>
-#include <stddef.h>
-
-#include "compat.h"
-
-char *
-strcasestr(const char *str1, const char *str2)
-{
- const char *s1;
- const char *s2;
-
- for (;;) {
- s1 = str1;
- s2 = str2;
- while (*s1 != '\0' && tolower(*s1) == tolower(*s2))
- s1++, s2++;
- if (*s2 == '\0')
- return (char *) str1;
- if (*s1 == '\0')
- return NULL;
- str1++;
- }
-
- return NULL;
-}
DIR diff --git a/src/compat/strlcpy.c b/src/compat/strlcpy.c
@@ -1,15 +0,0 @@
-#include "compat.h"
-
-#include <string.h>
-
-size_t
-strlcpy(char *buf, char const *str, size_t sz)
-{
- size_t len, cpy;
-
- len = strlen(str);
- cpy = (len > sz) ? (sz) : (len);
- memcpy(buf, str, cpy + 1);
- buf[sz - 1] = '\0';
- return len;
-}
DIR diff --git a/src/compat/strsep.c b/src/compat/strsep.c
@@ -1,23 +0,0 @@
-#include <string.h>
-
-#include "compat.h"
-
-char *
-strsep(char **str_p, char const *sep)
-{
- char *s, *prev;
-
- if (*str_p == NULL)
- return NULL;
-
- for (s = prev = *str_p; strchr(sep, *s) == NULL; s++)
- continue;
-
- if (*s == '\0') {
- *str_p = NULL;
- } else {
- *s = '\0';
- *str_p = s + 1;
- }
- return prev;
-}
DIR diff --git a/src/log.c b/src/log.c
@@ -1,72 +0,0 @@
-#include "log.h"
-
-#include <assert.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#ifndef LOG_DEFAULT
-#define LOG_DEFAULT 3 /* info */
-#endif
-
-char *arg0 = NULL;
-static int log_level = -1;
-
-void
-log_vprintf(int level, char const *flag, char const *fmt, va_list va)
-{
- char *env;
- int old_errno = errno;
-
- if (log_level < 0) {
- env = getenv("LOG");
- log_level = (env == NULL) ? 0 : atoi(env);
- if (log_level == 0)
- log_level = LOG_DEFAULT;
- }
-
- if (log_level < level)
- return;
-
- if (arg0 != NULL)
- fprintf(stderr, "%s: ", arg0);
-
- fprintf(stderr, "%s: ", flag);
- vfprintf(stderr, fmt, va);
-
- if (old_errno != 0)
- fprintf(stderr, ": %s", strerror(old_errno));
-
- fprintf(stderr, "\n");
- fflush(stderr);
-}
-
-void
-die(char const *fmt, ...)
-{
- va_list va;
- va_start(va, fmt); log_vprintf(1, "error", fmt, va); va_end(va);
- exit(1);
-}
-
-void
-warn(char const *fmt, ...)
-{
- va_list va;
- va_start(va, fmt); log_vprintf(2, "warn", fmt, va); va_end(va);
-}
-
-void
-info(char const *fmt, ...)
-{
- va_list va;
- va_start(va, fmt); log_vprintf(3, "info", fmt, va); va_end(va);
-}
-
-void
-debug(char const *fmt, ...)
-{
- va_list va;
- va_start(va, fmt); log_vprintf(4, "debug", fmt, va); va_end(va);
-}
DIR diff --git a/src/log.h b/src/log.h
@@ -1,14 +0,0 @@
-#ifndef LOG_H
-#define LOG_H
-
-#include <stdarg.h>
-
-/** src/log.c **/
-char *arg0;
-void log_vprintf(int level, char const *flag, char const *fmt, va_list va);
-void die(char const *fmt, ...);
-void warn(char const *fmt, ...);
-void info(char const *fmt, ...);
-void debug(char const *fmt, ...);
-
-#endif
DIR diff --git a/src/mem.c b/src/mem.c
@@ -1,160 +0,0 @@
-#include "mem.h"
-
-#include <assert.h>
-#include <errno.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-static struct mem_block *
-mem_block(void **memp)
-{
- struct mem_block *block = (void *)((char *)memp - sizeof *block);
-
- assert(memcmp(block->magic, MEM_BLOCK_MAGIC, 8) == 0);
- return block;
-}
-
-void *
-mem_alloc(struct mem_pool *pool, size_t len)
-{
- struct mem_block *block;
-
- block = calloc(1, sizeof *block + len);
- if (block == NULL)
- return NULL;
- memcpy(block->magic, MEM_BLOCK_MAGIC, 8);
-
- block->len = len;
- block->pool = pool;
- block->prev = NULL;
- block->next = pool->head;
- if (pool->head != NULL)
- pool->head->prev = block;
- pool->head = block;
-
- return block->buf;
-}
-
-int
-mem_resize(void **memp, size_t len)
-{
- struct mem_block *block = mem_block(*memp);
- int is_first = (block == block->pool->head);
- int is_same;
- void *v;
-
- v = realloc(block, sizeof *block + len);
- if (v == NULL)
- return -1;
- is_same = (block == v);
- block = v;
-
- block->len = len;
-
- if (is_same)
- return 0;
-
- if (block->prev != NULL)
- block->prev->next = v;
- if (block->next != NULL)
- block->next->prev = v;
- if (is_first)
- block->pool->head = v;
- *memp = block->buf;
-
- assert(memcmp(block->magic, MEM_BLOCK_MAGIC, 8) == 0);
- return 0;
-}
-
-int
-mem_grow(void **memp, size_t len)
-{
- assert(SIZE_MAX - len >= mem_block(*memp)->len);
-
- return mem_resize(memp, mem_length(*memp) + len);
-}
-
-int
-mem_shrink(void **memp, size_t len)
-{
- assert(mem_block(*memp)->len >= len);
-
- return mem_resize(memp, mem_length(*memp) - len);
-}
-
-size_t
-mem_length(void *mem)
-{
- return mem_block(mem)->len;
-}
-
-int
-mem_append(void **memp, void const *buf, size_t len)
-{
- size_t old_len = mem_length(*memp);
- struct mem_block *block;
-
- if (mem_grow(memp, len) < 0)
- return -1;
- block = mem_block(*memp);
- memcpy((char *)block->buf + old_len, buf, len);
-
- assert(memcmp(block->magic, MEM_BLOCK_MAGIC, 8) == 0);
- return 0;
-}
-
-int
-mem_read(void **memp, struct mem_pool *pool)
-{
- struct mem_block *block;
- ssize_t sz = 0;
- void *mem;
-
- mem = mem_alloc(pool, 0);
- if (mem == NULL)
- return -1;
-
- for (ssize_t r = 1; r > 0; sz += r) {
- if (mem_resize(&mem, sz + 2048) < 0)
- return -1;
-
- r = read(0, (char *)mem + sz, 2048);
- if (r < 0)
- return -1;
- }
- block = mem_block(mem);
- block->len = sz;
-
- *memp = mem;
- assert(memcmp(block->magic, MEM_BLOCK_MAGIC, 8) == 0);
- return 0;
-}
-
-void
-mem_delete(void *mem)
-{
- struct mem_block *block = mem_block(mem);;
-
- if (block == block->pool->head)
- block->pool->head = block->next;
- if (block->next != NULL)
- block->next->prev = block->prev;
- if (block->prev != NULL)
- block->prev->next = block->next;
- memset(block, 0, sizeof *block);
- free(block);
-}
-
-void
-mem_free(struct mem_pool *pool)
-{
- struct mem_block *block, *next;
-
- for (block = pool->head; block != NULL; block = next) {
- next = block->next;
- memset(block, 0, sizeof *block);
- free(block);
- }
-}
DIR diff --git a/src/mem.h b/src/mem.h
@@ -1,59 +0,0 @@
-#ifndef MEM_H
-#define MEM_H
-
-/*
- * Lightweight wrapper over malloc, that permit to define a memory pool of
- * multiple buffers, and free them all at once.
- *
- * *──────────┐
- * │ mem_pool │
- * ├──────────┤
- * │*head │
- * └┬─────────┘
- * v
- * NULL< *───────────┐< >*───────────┐< >*───────────┐< >*───────────┐ >NULL
- * \ │ mem_block │ \/ │ mem_block │ \/ │ mem_block │ \/ │ mem_block │ /
- * \ ├───────────┤ /\ ├───────────┤ /\ ├───────────┤ /\ ├───────────┤ /
- * `┤*prev *next├' `┤*prev *next├' `┤*prev *next├' `┤*prev *next├'
- * │len │ │len │ │len │ │len │
- * ├─┴─magic───┤ ├─┴─magic───┤ ├─┴─magic───┤ ├─┴─magic───┤
- * │///////////│ │///////////│ │///////////│ │///////////│
- * │///////////│ │///////////│ │///////////│ │///////////│
- * │///////////│ │///////////│ │///////////│ └───────────┘
- * └───────────┘ │///////////│ │///////////│
- * │///////////│ └───────────┘
- * └───────────┘
- *
- * This permits the type checker to still work on all operations while
- * providing generic memory management functions for all types of data
- * structures and keep track of each object's length.
- */
-
-#include <stddef.h>
-
-#define MEM_BLOCK_MAGIC "\xcc\x68\x23\xd7\x9b\x7d\x39\xb9"
-
-struct mem_pool {
- struct mem_block *head;
-};
-
-struct mem_block {
- struct mem_pool *pool;
- struct mem_block *prev, *next;
- size_t len;
- char magic[8]; /* at the end to detect buffer underflow */
- char buf[];
-};
-
-/** src/mem.c **/
-void * mem_alloc(struct mem_pool *pool, size_t len);
-int mem_resize(void **memp, size_t len);
-int mem_grow(void **memp, size_t len);
-int mem_shrink(void **memp, size_t len);
-size_t mem_length(void *mem);
-int mem_append(void **memp, void const *buf, size_t len);
-int mem_read(void **memp, struct mem_pool *pool);
-void mem_delete(void *mem);
-void mem_free(struct mem_pool *pool);
-
-#endif
DIR diff --git a/src/term.c b/src/term.c
@@ -1,107 +0,0 @@
-#include "term.h"
-
-#include <ctype.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <termios.h>
-
-#include "compat.h"
-#include "utf8.h"
-
-struct term term = {0};
-
-static int
-term_codepoint_width(uint32_t codepoint, int pos)
-{
- if (codepoint == '\t')
- return 8 - pos % 8;
- return wcwidth(codepoint);
-}
-
-int
-term_at_width(char const *s, int width, int pos)
-{
- char const *beg = s;
-
- for (uint32_t state = 0, codepoint; *s != '\0'; s++) {
- if (utf8_decode(&state, &codepoint, *s) == UTF8_ACCEPT) {
- pos += term_codepoint_width(codepoint, pos);
- if (pos > width)
- break;
- }
- }
- return s - beg;
-}
-
-int
-term_raw_on(int fd)
-{
- struct termios new_termios = {0};
- char *seq = "\x1b[s\x1b[?1049h\x1b[H";
- ssize_t len = strlen(seq);
-
- if (write(fd, seq, len) < len)
- return -1;
-
- if (tcgetattr(fd, &term.old_termios) < 0)
- return -1;
- memcpy(&new_termios, &term.old_termios, sizeof new_termios);
-
- new_termios.c_lflag &= ~(ICANON | ECHO | IEXTEN | IGNBRK | ISIG);
- if (tcsetattr(fd, TCSANOW, &new_termios) == -1)
- return -1;
- return 0;
-}
-
-int
-term_raw_off(int fd)
-{
- char *seq = "\x1b[2J\x1b[u\033[?1049l";
- ssize_t len = strlen(seq);
-
- if (tcsetattr(fd, TCSANOW, &term.old_termios) < 0)
- return -1;
- if (write(fd, seq, len) < len)
- return -1;
- return 0;
-}
-
-int
-term_get_key(FILE *fp)
-{
- int key, num;
-
- key = fgetc(fp);
-top:
- switch (key) {
- case EOF:
- return -1;
- case TERM_KEY_ALT('['):
- key = getc(fp);
- if (key == EOF)
- return -1;
-
- for (num = 0; isdigit(key);) {
- num *= 10;
- num += key - '0';
-
- key = fgetc(fp);
- if (key == EOF)
- return -1;
- }
-
- key = TERM_KEY_CSI(key, num);
-
- goto top;
- case TERM_KEY_ESC:
- key = getc(fp);
- if (key == EOF)
- return -1;
- key = TERM_KEY_ALT(key);
- goto top;
- default:
- return key;
- }
-}
DIR diff --git a/src/term.h b/src/term.h
@@ -1,39 +0,0 @@
-#ifndef TERM_H
-#define TERM_H
-
-#include <stdint.h>
-#include <stdio.h>
-#include <sys/ioctl.h>
-#include <termios.h>
-#include <unistd.h>
-
-#define TERM_KEY_CTRL(c) ((c) & ~0x40)
-#define TERM_KEY_ALT(c) (0x100 + (c))
-#define TERM_KEY_CSI(c, i) (0x100 + (c) * 0x100 + (i))
-
-enum term_key {
- TERM_KEY_ESC = 0x1b,
- TERM_KEY_DELETE = 127,
- TERM_KEY_BACKSPACE = TERM_KEY_CTRL('H'),
- TERM_KEY_TAB = TERM_KEY_CTRL('I'),
- TERM_KEY_ENTER = TERM_KEY_CTRL('J'),
- TERM_KEY_ARROW_UP = TERM_KEY_CSI('A', 0),
- TERM_KEY_ARROW_DOWN = TERM_KEY_CSI('B', 0),
- TERM_KEY_PAGE_UP = TERM_KEY_CSI('~', 5),
- TERM_KEY_PAGE_DOWN = TERM_KEY_CSI('~', 6),
-};
-
-struct term {
- struct winsize winsize;
- struct termios old_termios;
-};
-
-/** src/term.c **/
-struct term term;
-int term_width_at_pos(uint32_t codepoint, int pos);
-int term_at_width(char const *s, int width, int pos);
-int term_raw_on(int fd);
-int term_raw_off(int fd);
-int term_get_key(FILE *fp);
-
-#endif
DIR diff --git a/term.c b/term.c
@@ -0,0 +1,107 @@
+#include "term.h"
+
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+
+#include "wcwidth.h"
+#include "utf8.h"
+
+struct term term = {0};
+
+static int
+term_codepoint_width(uint32_t codepoint, int pos)
+{
+ if (codepoint == '\t')
+ return 8 - pos % 8;
+ return wcwidth(codepoint);
+}
+
+int
+term_at_width(char const *s, int width, int pos)
+{
+ char const *beg = s;
+
+ for (uint32_t state = 0, codepoint; *s != '\0'; s++) {
+ if (utf8_decode(&state, &codepoint, *s) == UTF8_ACCEPT) {
+ pos += term_codepoint_width(codepoint, pos);
+ if (pos > width)
+ break;
+ }
+ }
+ return s - beg;
+}
+
+int
+term_raw_on(int fd)
+{
+ struct termios new_termios = {0};
+ char *seq = "\x1b[s\x1b[?1049h\x1b[H";
+ ssize_t len = strlen(seq);
+
+ if (write(fd, seq, len) < len)
+ return -1;
+
+ if (tcgetattr(fd, &term.old_termios) < 0)
+ return -1;
+ memcpy(&new_termios, &term.old_termios, sizeof new_termios);
+
+ new_termios.c_lflag &= ~(ICANON | ECHO | IEXTEN | IGNBRK | ISIG);
+ if (tcsetattr(fd, TCSANOW, &new_termios) == -1)
+ return -1;
+ return 0;
+}
+
+int
+term_raw_off(int fd)
+{
+ char *seq = "\x1b[2J\x1b[u\033[?1049l";
+ ssize_t len = strlen(seq);
+
+ if (tcsetattr(fd, TCSANOW, &term.old_termios) < 0)
+ return -1;
+ if (write(fd, seq, len) < len)
+ return -1;
+ return 0;
+}
+
+int
+term_get_key(FILE *fp)
+{
+ int key, num;
+
+ key = fgetc(fp);
+top:
+ switch (key) {
+ case EOF:
+ return -1;
+ case TERM_KEY_ALT('['):
+ key = getc(fp);
+ if (key == EOF)
+ return -1;
+
+ for (num = 0; isdigit(key);) {
+ num *= 10;
+ num += key - '0';
+
+ key = fgetc(fp);
+ if (key == EOF)
+ return -1;
+ }
+
+ key = TERM_KEY_CSI(key, num);
+
+ goto top;
+ case TERM_KEY_ESC:
+ key = getc(fp);
+ if (key == EOF)
+ return -1;
+ key = TERM_KEY_ALT(key);
+ goto top;
+ default:
+ return key;
+ }
+}
DIR diff --git a/term.h b/term.h
@@ -0,0 +1,38 @@
+#ifndef TERM_H
+#define TERM_H
+
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#define TERM_KEY_CTRL(c) ((c) & ~0x40)
+#define TERM_KEY_ALT(c) (0x100 + (c))
+#define TERM_KEY_CSI(c, i) (0x100 + (c) * 0x100 + (i))
+
+enum term_key {
+ TERM_KEY_ESC = 0x1b,
+ TERM_KEY_DELETE = 127,
+ TERM_KEY_BACKSPACE = TERM_KEY_CTRL('H'),
+ TERM_KEY_TAB = TERM_KEY_CTRL('I'),
+ TERM_KEY_ENTER = TERM_KEY_CTRL('J'),
+ TERM_KEY_ARROW_UP = TERM_KEY_CSI('A', 0),
+ TERM_KEY_ARROW_DOWN = TERM_KEY_CSI('B', 0),
+ TERM_KEY_PAGE_UP = TERM_KEY_CSI('~', 5),
+ TERM_KEY_PAGE_DOWN = TERM_KEY_CSI('~', 6),
+};
+
+struct term {
+ struct winsize winsize;
+ struct termios old_termios;
+};
+
+struct term term;
+int term_width_at_pos(uint32_t codepoint, int pos);
+int term_at_width(char const *s, int width, int pos);
+int term_raw_on(int fd);
+int term_raw_off(int fd);
+int term_get_key(FILE *fp);
+
+#endif
DIR diff --git a/src/utf8.c b/utf8.c
DIR diff --git a/src/utf8.h b/utf8.h
DIR diff --git a/src/compat/wcwidth.c b/wcwidth.c
DIR diff --git a/wcwidth.h b/wcwidth.h
@@ -0,0 +1,13 @@
+#ifndef WCWIDTH_H
+#define WCWIDTH_H
+
+#include <wchar.h>
+
+#define wcwidth(c) mk_wcwidth_cjk(c)
+
+int mk_wcwidth(wchar_t ucs);
+int mk_wcswidth(const wchar_t *pwcs, size_t n);
+int mk_wcwidth_cjk(wchar_t ucs);
+int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n);
+
+#endif