iomenu

interactive terminal dmenu-style menu
Log | Files | Refs | README | LICENSE

commit 774936ea25643995d42c2ca1cd7052875fc35573
parent f099742c20929af4c304bb477ad8ec42e0bb6f17
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:
MMakefile | 9++++-----
Acompat.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/compat.h -> compat.h | 0
Miomenu.c | 129++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Dsrc/compat/strcasestr.c | 25-------------------------
Dsrc/compat/strlcpy.c | 15---------------
Dsrc/compat/strsep.c | 23-----------------------
Dsrc/log.c | 72------------------------------------------------------------------------
Dsrc/log.h | 14--------------
Dsrc/mem.c | 160-------------------------------------------------------------------------------
Dsrc/mem.h | 59-----------------------------------------------------------
Dsrc/term.c | 107-------------------------------------------------------------------------------
Dsrc/term.h | 39---------------------------------------
Aterm.c | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aterm.h | 38++++++++++++++++++++++++++++++++++++++
Rsrc/utf8.c -> utf8.c | 0
Rsrc/utf8.h -> utf8.h | 0
Rsrc/compat/wcwidth.c -> wcwidth.c | 0
Awcwidth.h | 13+++++++++++++
19 files changed, 299 insertions(+), 570 deletions(-)

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 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; +} diff --git a/src/compat.h b/compat.h 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; } 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; -} 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; -} 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; -} 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); -} 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 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); - } -} 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 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; - } -} 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 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; + } +} 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 diff --git a/src/utf8.c b/utf8.c diff --git a/src/utf8.h b/utf8.h diff --git a/src/compat/wcwidth.c b/wcwidth.c 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