tmus

TUI Music Player
git clone https://git.sinitax.com/sinitax/tmus
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

commit d68d0f39ab7675745e7d177f6774736f7ec58783
parent 02141177f8055a2d0edace481d631adbcb4b4c47
Author: Louis Burda <quent.burda@gmail.com>
Date:   Wed,  1 Dec 2021 00:31:01 +0100

Replace readline with own implementation for integration

Diffstat:
M.gitignore | 1+
MMakefile | 7+++++--
Ahistory.c | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahistory.h | 40++++++++++++++++++++++++++++++++++++++++
Alink.c | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alink.h | 23+++++++++++++++++++++++
Dmain | 0
Mmain.c | 319+++++++++++++++++++++++++++++++++----------------------------------------------
Autil.c | 43+++++++++++++++++++++++++++++++++++++++++++
Autil.h | 12++++++++++++
10 files changed, 495 insertions(+), 188 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1,3 @@ vgcore* main +*.o diff --git a/Makefile b/Makefile @@ -8,5 +8,8 @@ all: main clean: rm main -main: main.c - $(CC) -o $@ $< $(CFLAGS) $(LDLIBS) +%.o: %.c %.h + $(CC) -c -o $@ $< $(CFLAGS) $(LDLIBS) + +main: main.c util.o history.o link.o + $(CC) -o $@ $^ $(CFLAGS) $(LDLIBS) diff --git a/history.c b/history.c @@ -0,0 +1,184 @@ +#include "history.h" +#include "link.h" +#include "util.h" + +#include <string.h> +#include <wchar.h> + + +void +history_init(struct history *history) +{ + history->list = LIST_HEAD; + history->query = inputln_init(); + history->cmd = history->query; +} + +void +history_submit(struct history *history) +{ + /* if chose from history free query */ + if (history->cmd != history->query) { + history_pop(history->query); + inputln_free(history->query); + } + + /* pop first in case already in history */ + history_pop(history->cmd); + history_add(history, history->cmd); + + /* create new input buf and add to hist */ + history->query = inputln_init(); + history->cmd = history->query; + history_add(history, history->cmd); +} + +void +history_free(struct history *history) +{ + struct link *iter, *next; + struct inputln *ln; + + for (iter = &history->list; iter; iter = next) { + next = iter->next; + ln = UPCAST(iter, struct inputln); + free(ln); + } + free(history->cmd); +} + +struct inputln * +history_list_prev(struct inputln *cur, const wchar_t *search) +{ + struct link *iter; + struct inputln *ln; + + for (iter = cur->link.prev; iter && iter->prev; iter = iter->prev) { + ln = UPCAST(iter, struct inputln); + if (!search || !*search || wcsstr(ln->buf, search)) + return ln; + } + + return NULL; +} + +struct inputln * +history_list_next(struct inputln *cur, const wchar_t *search) +{ + struct link *iter; + struct inputln *ln; + + for (iter = cur->link.next; iter; iter = iter->next) { + ln = UPCAST(iter, struct inputln); + if (!search || !*search || wcsstr(ln->buf, search)) + return ln; + } + + return cur; +} + +void +history_prev(struct history *history) +{ + history->cmd = history_list_prev(history->cmd, history->query->buf); + if (!history->cmd) history->cmd = history->query; +} + +void +history_next(struct history *history) +{ + history->cmd = history_list_next(history->cmd, history->query->buf); +} + +void +history_pop(struct inputln *line) +{ + link_pop(&line->link); +} + +void +history_add(struct history *history, struct inputln *line) +{ + struct inputln *ln; + struct link *back; + + if (list_len(&history->list) == HISTORY_MAX) { + back = list_back(&history->list); + back->prev->next = NULL; + ln = UPCAST(back, struct inputln); + inputln_free(ln); + } + + link_append(&history->list, &line->link); +} + +struct inputln * +inputln_init(void) +{ + struct inputln *ln; + + ln = malloc(sizeof(struct inputln)); + ASSERT(ln != NULL); + ln->cap = 128; + ln->buf = malloc(ln->cap * sizeof(wchar_t)); + ASSERT(ln->buf != NULL); + ln->len = 0; + ln->buf[ln->len] = '\0'; + ln->cur = 0; + ln->link.next = NULL; + ln->link.prev = NULL; + + return ln; +} + +void +inputln_free(struct inputln *ln) +{ + free(ln->buf); + free(ln); +} + +void +inputln_left(struct inputln *cmd) +{ + cmd->cur = MAX(0, cmd->cur-1); +} + +void +inputln_right(struct inputln *cmd) +{ + cmd->cur = MIN(cmd->len, cmd->cur+1); +} + +void +inputln_addch(struct inputln *line, wchar_t c) +{ + int i; + + if (line->len + 1 == line->cap) { + line->cap *= 2; + line->buf = realloc(line->buf, line->cap); + } + + for (i = line->len; i > line->cur; i--) + line->buf[i] = line->buf[i-1]; + line->buf[line->cur] = c; + line->len++; + line->cur++; +} + +void +inputln_del(struct inputln *line, int n) +{ + int i; + + if (!line->cur) return; + + n = MIN(n, line->cur); + for (i = line->cur; i <= line->len; i++) + line->buf[i-n] = line->buf[i]; + for (i = line->len - n; i <= line->len; i++) + line->buf[i] = 0; + line->len -= n; + line->cur -= n; +} diff --git a/history.h b/history.h @@ -0,0 +1,40 @@ +#pragma once + +#include "link.h" + +#include "wchar.h" + +#define HISTORY_MAX 100 + +struct inputln { + wchar_t *buf; + int len, cap; + int cur; + + struct link link; +}; + +struct history { + struct link list; + struct inputln *cmd, *query; +}; + +void history_init(struct history *history); +void history_free(struct history *history); + +void history_submit(struct history *history); + +void history_prev(struct history *history); +void history_next(struct history *history); + +void history_add(struct history *history, struct inputln *line); +void history_pop(struct inputln *line); + +struct inputln *inputln_init(void); +void inputln_free(struct inputln *ln); + +void inputln_left(struct inputln *line); +void inputln_right(struct inputln *line); + +void inputln_addch(struct inputln *line, wchar_t c); +void inputln_del(struct inputln *line, int n); diff --git a/link.c b/link.c @@ -0,0 +1,54 @@ +#include "link.h" + +int +list_len(struct link *list) +{ + struct link *iter; + int len; + + len = 0; + for (iter = list; iter; iter = iter->next) + len += 1; + + return len; +} + +struct link * +list_back(struct link *link) +{ + for (; link->next; link = link->next); + return link; +} + +void +link_prepend(struct link *list, struct link *link) +{ + link->prev = list->prev; + link->next = list; + + if (link->prev) + link->prev->next = link; + if (link->next) + link->next->prev = link; +} + +void +link_append(struct link *list, struct link *link) +{ + link->prev = list; + link->next = list->next; + + if (link->prev) + link->prev->next = link; + if (link->next) + link->next->prev = link; +} + +void +link_pop(struct link *link) +{ + if (link->prev) + link->prev->next = link->next; + if (link->next) + link->next->prev = link->prev; +} diff --git a/link.h b/link.h @@ -0,0 +1,23 @@ +#pragma once + +#include <stdlib.h> + +#define OFFSET(type, attr) ((size_t) &((type *)0)->attr) +#define UPCAST(ptr, type) ({ \ + const typeof( ((type *)0)->link ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - OFFSET(type, link) ); }) + +#define LIST_HEAD ((struct link) { .prev = NULL, .next = NULL }) + +struct link { + struct link *prev, *next; +}; + +int list_len(struct link *list); +struct link* list_back(struct link *list); + +void link_push_back(struct link *list, struct link *link); + +void link_prepend(struct link *list, struct link *link); +void link_append(struct link *list, struct link *link); +void link_pop(struct link *link); diff --git a/main b/main Binary files differ. diff --git a/main.c b/main.c @@ -1,27 +1,19 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 600 + +#include "util.h" +#include "link.h" +#include "history.h" + +#include "curses.h" -#include <stdlib.h> #include <stdio.h> -#include <signal.h> +#include <stdlib.h> #include <string.h> - -#include <unistd.h> #include <time.h> +#include <unistd.h> #include <wchar.h> #include <wctype.h> -#include "ncurses.h" -#include "readline/readline.h" -#include "readline/history.h" - -#define MAX(a, b) ((a) > (b) ? (a) : (b)) -#define MIN(a, b) ((a) > (b) ? (b) : (a)) -#define ARRLEN(x) (sizeof(x)/sizeof((x)[0])) - -#define OFFSET(parent, attr) ((size_t) &((type *)0)->attr) -#define UPCAST(ptr, type) ({ \ - const typeof( ((type *)0)->link ) *__mptr = (ptr); \ - (type *)( (char *)__mptr - OFFSET(type, link) ); }) #define ATTR_ON(win, attr) wattr_on((win), (attr), NULL) #define ATTR_OFF(win, attr) wattr_off((win), (attr), NULL) @@ -30,16 +22,27 @@ #define KEY_ESC '\x1b' #define KEY_TAB '\t' +#define KEY_CTRL(c) ((c) & ~0x60) -struct pane; -typedef int (*pane_handler)(int c); -typedef void (*pane_updater)(struct pane *pane, int sel); +enum { + STYLE_DEFAULT, + STYLE_TITLE, + STYLE_PANE_SEP +}; -struct link { - struct link *prev, *next; +enum { + EXECUTE, + SEARCH }; +struct pane; + +typedef int (*pane_handler)(wint_t c); +typedef void (*pane_updater)(struct pane *pane, int sel); +typedef char *(*completion_generator)(const char *text, int state); +typedef int (*cmd_handler)(const char *args); + struct pane { WINDOW *win; int sx, sy, ex, ey; @@ -71,45 +74,38 @@ struct tag { struct link link; }; -enum { - STYLE_DEFAULT, - STYLE_TITLE, - STYLE_PANE_SEP +struct cmd { + const char *name; + cmd_handler func; }; -enum { - EXECUTE, - SEARCH -}; int scrw, scrh; int quit; -struct pane pane_left, pane_right, pane_bot; -struct pane *pane_sel; float win_ratio; +struct pane *pane_sel; +struct pane pane_left, pane_right, pane_bot; +struct pane *const panes[] = { + &pane_left, + &pane_right, + &pane_bot +}; struct track *track_sel; struct track *tracks; -struct track_ref *playlist; +struct link playlist; int track_paused; -int cmdshow, cmdcur; -int cmdmode; +completion_generator completion; +struct history search_history, command_history; +struct history *history; +int cmd_show, cmd_mode; -int rl_input, rl_input_avail; - -struct pane *const panes[] = { - &pane_left, - &pane_right, - &pane_bot -}; void init(void); void cleanup(void); -void cancel(int sig); - void resize(void); void pane_resize(struct pane *pane, int sx, int sy, int ex, int ey); @@ -117,56 +113,28 @@ void pane_resize(struct pane *pane, void pane_init(struct pane *p, pane_handler handle, pane_updater update); void pane_title(struct pane *pane, const char *title, int highlight); -char **track_name_completion(const char *text, int start, int end); +char *command_name_generator(const char *text, int state); char *track_name_generator(const char *text, int state); -int readline_getc(FILE *f); -int readline_input_avail(void); -void readline_consume(int c); -int exit_cmdmode(int a, int b); - -int strnwidth(const char *str, int len); - -void evalcmd(char *cmd); - -int track_input(int c); +int track_input(wint_t c); void track_vis(struct pane *pane, int sel); -int tag_input(int c); +int tag_input(wint_t c); void tag_vis(struct pane *pane, int sel); -int cmd_input(int c); +int cmd_input(wint_t c); void cmd_vis(struct pane *pane, int sel); -void main_input(int c); +void main_input(wint_t c); void main_vis(void); + void init(void) { quit = 0; win_ratio = 0.3f; - cmdshow = 0; - cmdcur = 0; - - /* readline */ - rl_initialize(); - rl_input = 0; - rl_input_avail = 0; - - rl_catch_signals = 0; - rl_catch_sigwinch = 0; - rl_deprep_term_function = NULL; - rl_prep_term_function = NULL; - rl_change_environment = 0; - - rl_getc_function = readline_getc; - rl_input_available_hook = readline_input_avail; - rl_callback_handler_install("", evalcmd); - rl_attempted_completion_function = track_name_completion; - rl_bind_key('\x07', exit_cmdmode); - - /* curses */ + initscr(); cbreak(); noecho(); @@ -176,7 +144,9 @@ init(void) curs_set(0); ESCDELAY = 0; - signal(SIGINT, cancel); + history = &command_history; + history_init(&search_history); + history_init(&command_history); init_pair(STYLE_TITLE, COLOR_WHITE, COLOR_BLUE); init_pair(STYLE_PANE_SEP, COLOR_BLUE, COLOR_BLACK); @@ -193,7 +163,9 @@ init(void) void cleanup(void) { - rl_callback_handler_remove(); + history_free(&search_history); + history_free(&command_history); + delwin(pane_left.win); delwin(pane_right.win); delwin(pane_bot.win); @@ -201,16 +173,6 @@ cleanup(void) } void -cancel(int sig) -{ - if (pane_sel == &pane_bot) { - pane_sel = NULL; - } else { - quit = 1; - } -} - -void resize(void) { int i; @@ -240,7 +202,6 @@ pane_resize(struct pane *pane, int sx, int sy, int ex, int ey) pane->h = pane->ey - pane->sy; pane->active = (pane->w > 0 && pane->h > 0); - if (pane->active) { wresize(pane->win, pane->h, pane->w); mvwin(pane->win, pane->sy, pane->sx); @@ -252,6 +213,7 @@ void pane_init(struct pane *pane, pane_handler handle, pane_updater update) { pane->win = newwin(1, 1, 0, 0); + ASSERT(pane->win != NULL); pane->handle = handle; pane->update = update; } @@ -272,28 +234,28 @@ pane_title(struct pane *pane, const char *title, int highlight) STYLE_OFF(pane->win, TITLE); } -char ** -track_name_completion(const char *text, int start, int end) +char * +command_name_generator(const char *text, int reset) { - rl_attempted_completion_over = 1; - return rl_completion_matches(text, track_name_generator); + return NULL; } char * -track_name_generator(const char *text, int state) +track_name_generator(const char *text, int reset) { - static int list_index, len; + static int index, len; char *name; - if (cmdmode != SEARCH) return NULL; + if (cmd_mode != SEARCH) return NULL; - if (!state) { - list_index = 0; + if (reset) { + index = 0; len = strlen(text); } - if (list_index++ == 0 && !strncmp("hello", text, len)) + if (index++ == 0 && !strncmp("hello", text, len)) return strdup("hello"); + // TODO: iter over playlist // while ((name = track_names[list_index++])) { // if (strncmp(name, text, len) == 0) { @@ -304,88 +266,33 @@ track_name_generator(const char *text, int state) return NULL; } -int -readline_input_avail(void) -{ - return rl_input_avail; -} - -int -readline_getc(FILE *dummy) -{ - rl_input_avail = false; - return rl_input; -} - void -readline_consume(int c) +tag_init(void) { - rl_input = c; - rl_input_avail = true; - rl_callback_read_char(); + /* TODO: load tags as directory names (excluding unsorted) */ } int -exit_cmdmode(int a, int b) +tag_input(wint_t c) { - pane_sel = NULL; - move(5, 5); - wprintw(stdscr, "HIT %i %i", a, b); return 0; } -int -strnwidth(const char *s, int n) -{ - mbstate_t shift_state; - wchar_t wc; - size_t wc_len; - size_t width = 0; - - memset(&shift_state, '\0', sizeof shift_state); - - for (size_t i = 0; i < n; i += wc_len) { - wc_len = mbrtowc(&wc, s + i, MB_CUR_MAX, &shift_state); - if (!wc_len) { - break; - } else if (wc_len >= (size_t)-2) { - width += MIN(n - 1, strlen(s + i)); - break; - } else { - width += iswcntrl(wc) ? 2 : MAX(0, wcwidth(wc)); - } - } - -done: - return width; -} - void -evalcmd(char *line) -{ - if (!line || !*line) { - pane_sel = NULL; - return; - } - - if (*line) add_history(line); -} - -int -tag_input(int c) +tag_vis(struct pane *pane, int sel) { - return 0; + werase(pane->win); + pane_title(pane, "Tags", sel); } void -tag_vis(struct pane *pane, int sel) +track_init(void) { - werase(pane->win); - pane_title(pane, "Tags", sel); + /* TODO: for each tag director load track names */ } int -track_input(int c) +track_input(wint_t c) { return 0; } @@ -398,16 +305,59 @@ track_vis(struct pane *pane, int sel) } int -cmd_input(int c) +cmd_input(wint_t c) { - readline_consume(c); + if (cmd_mode == EXECUTE) { + history = &command_history; + completion = command_name_generator; + } else { + history = &search_history; + completion = track_name_generator; + } + + switch (c) { + case KEY_ESC: + if (history->cmd == history->query) + return 0; + history->cmd = history->query; + break; + case KEY_LEFT: + inputln_left(history->cmd); + break; + case KEY_RIGHT: + inputln_right(history->cmd); + break; + case KEY_CTRL('w'): + inputln_del(history->cmd, history->cmd->cur); + break; + case KEY_UP: + // TODO: show visually that no more matches + history_next(history); + break; + case KEY_DOWN: + history_prev(history); + break; + case '\n': + case KEY_ENTER: + history_submit(history); + break; + case KEY_TAB: + break; + case KEY_BACKSPACE: + inputln_del(history->cmd, 1); + break; + default: + if (!iswprint(c)) return 0; + inputln_addch(history->cmd, c); + break; + } return 1; } void cmd_vis(struct pane *pane, int sel) { - int in_range; + struct inputln *cmd; werase(pane->win); @@ -416,23 +366,23 @@ cmd_vis(struct pane *pane, int sel) else pane_title(pane, "", 0); - if (sel || cmdshow) { + if (sel || cmd_show) { wmove(pane->win, 2, 0); - waddch(pane->win, cmdmode == SEARCH ? '/' : ':'); - wprintw(pane->win, "%-*.*s", pane->w - 1, pane->w - 1, rl_line_buffer); + waddch(pane->win, cmd_mode == SEARCH ? '/' : ':'); + cmd = history->cmd; + wprintw(pane->win, "%-*.*ls", pane->w - 1, pane->w - 1, cmd->buf); + // TODO: if query != cmd, highlight query substr if (sel) { - cmdcur = strnwidth(rl_line_buffer, rl_point); ATTR_ON(pane->win, A_REVERSE); - wmove(pane->win, 2, 1 + cmdcur); - in_range = cmdcur < strlen(rl_line_buffer); - waddch(pane->win, in_range ? rl_line_buffer[cmdcur] : ' '); + wmove(pane->win, 2, 1 + cmd->cur); + waddch(pane->win, cmd->cur < cmd->len ? cmd->buf[cmd->cur] : ' '); ATTR_OFF(pane->win, A_REVERSE); } } } void -main_input(int c) +main_input(wint_t c) { switch (c) { case KEY_TAB: @@ -450,16 +400,12 @@ main_input(int c) case KEY_RIGHT: pane_sel = &pane_right; break; - case CTRL('c'): - quit = 1; - break; case ':': - cmdmode = EXECUTE; + cmd_mode = EXECUTE; pane_sel = &pane_bot; break; - case '?': case '/': - cmdmode = SEARCH; + cmd_mode = SEARCH; pane_sel = &pane_bot; break; } @@ -486,7 +432,8 @@ main_vis(void) int main(int argc, const char **argv) { - int i, c, handled; + int i, handled; + wint_t c; init(); @@ -511,9 +458,9 @@ main(int argc, const char **argv) main_vis(); doupdate(); - keypad(stdscr, pane_sel != &pane_bot); - c = getch(); + get_wch(&c); } while (!quit); cleanup(); } + diff --git a/util.c b/util.c @@ -0,0 +1,43 @@ +#define _XOPEN_SOURCE 600 + +#include "util.h" + +#include <stdlib.h> +#include <string.h> +#include <wchar.h> +#include <wctype.h> + +int +strnwidth(const char *s, int n) +{ + mbstate_t shift_state; + wchar_t wc; + size_t wc_len; + size_t width = 0; + + memset(&shift_state, '\0', sizeof shift_state); + + for (size_t i = 0; i < n; i += wc_len) { + wc_len = mbrtowc(&wc, s + i, MB_CUR_MAX, &shift_state); + if (!wc_len) { + break; + } else if (wc_len >= (size_t)-2) { + width += MIN(n - 1, strlen(s + i)); + break; + } else { + width += iswcntrl(wc) ? 2 : MAX(0, wcwidth(wc)); + } + } + +done: + return width; +} + +void +assert(int cond, const char *file, int line, const char *condstr) +{ + if (cond) return; + + fprintf(stderr, "Assertion failed %s:%i (%s)", file, line, condstr); + exit(1); +} diff --git a/util.h b/util.h @@ -0,0 +1,12 @@ +#pragma once + +#include <stdio.h> + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) > (b) ? (b) : (a)) +#define ARRLEN(x) (sizeof(x)/sizeof((x)[0])) + +#define ASSERT(x) assert((x), __FILE__, __LINE__, #x) + +int strnwidth(const char *s, int n); +void assert(int cond, const char *file, int line, const char *condstr);