#include "tui.h" #include #include #include #include "caret_notater.h" extern bool do_execute; /* I fucking hate readline. * Apparently the only way to set an initial value is using a hook. * What makes this extra painful is that readline cannot be explicitly * initialized nor is it documented clearly that shit will segfault * otherwise. * If I ever find out what is a sensible alternative im ditching it forever. */ const char * initial_text; const char * get_input_line(void) { return rl_line_buffer; } /* "Cursor" position; the entry selected by the user */ size_t selection_relative = 0; size_t selection_offset = 0; size_t entry_lines; bool is_input_changed = true; bool do_redisplay = true; /* Counter to signal which line to print to, * and saved counterpart which is used to * determine how many lines were printed. */ static size_t entry_line_index = 0; static size_t last_entry_line_index = 0; static char version_string[] = # include "version.inc" ; // Ncurses fun static WINDOW * main_window; static WINDOW * entry_window; static WINDOW * input_window; static WINDOW * version_window; // Readline requirements static int input_available = false; static int input; // static inline void update_input(void); static void full_redraw(void); static bool do_fullredraw = true; int init_tui(void) { // Ncurses initscr(); nonl(); cbreak(); noecho(); curs_set(0); keypad(stdscr, TRUE); entry_lines = LINES-3; main_window = newwin(LINES-1, COLS, 0, 0); input_window = newwin( 1, COLS, LINES-1, 0); entry_window = subwin(main_window, entry_lines, COLS-2, 1, 1); version_window = subwin(main_window, 1, strlen(version_string), 0, 5); refresh(); // Readline rl_bind_key('\t', rl_insert); rl_catch_signals = 0; rl_catch_sigwinch = 0; rl_change_environment = 0; rl_prep_term_function = NULL; rl_deprep_term_function = NULL; int getc_function([[maybe_unused]] FILE* ignore) { input_available = false; return (int)(input); } int return_input_available(void) { return input_available; } int initializer(void) { rl_insert_text(initial_text); return 0; } rl_getc_function = getc_function; rl_input_available_hook = return_input_available; rl_startup_hook = initializer; /* Due to this bug: https://mail.gnu.org/archive/html/bug-readline/2013-09/msg00021.html ; * we cannot null this function. * Im seriously questioning why readline is still the """default""" library in the wild * and whether i should participate. */ void redisplay_nop(void) { return; } rl_redisplay_function = redisplay_nop; /* We must specify an input handler or readline chimps out, * but we dont want the line to be actually submittable, * (search is continous and that would delete what the user * has typedso far) * so we also override enter to do nothing. */ void no_op_handler([[maybe_unused]] char *line) { return; } int no_op_bind([[maybe_unused]] int i, [[maybe_unused]] int h) { return 0; } rl_callback_handler_install("", no_op_handler); rl_bind_key('\n', no_op_bind); return 0; } int deinit_tui(void) { endwin(); return 0; } void tui_append_back(const entry_t entry) { const int TIME_SIZE = 19 + 1; char time_buffer[TIME_SIZE]; strftime(time_buffer, TIME_SIZE, "%Y-%m-%d %H:%M:%S", localtime((time_t*)&entry.timestamp)); char caret_notation_buffer[(strlen(entry.command)*2)+1]; const size_t current_line_y = (entry_lines-1)-entry_line_index; if (entry_line_index == selection_relative) { wattron(entry_window, A_REVERSE); } mvwprintw(entry_window, current_line_y, 0, "%s %.*s", time_buffer, getmaxx(entry_window) - (TIME_SIZE-1) - /*space-padding*/2, string_to_caret_notation(entry.command, strlen(entry.command), caret_notation_buffer ) ); if (entry_line_index == selection_relative) { wattroff(entry_window, A_REVERSE); } /* Only delete to the end of the line if the cursor did not overflow * to the line below from a long entry. */ if ((size_t)getcury(entry_window) == current_line_y) { wclrtoeol(entry_window); } ++entry_line_index; } static void full_redraw(void) { box(main_window, 0, 0); waddstr(version_window, version_string); update_input(); wnoutrefresh(version_window); doupdate(); } void tui_rearm() { entry_line_index = 0; } static inline void update_input() { const char * const prompt = "$ "; wmove(input_window, 0, 0); waddstr(input_window, prompt); waddnstr(input_window, rl_line_buffer, rl_point); wattron(input_window, A_REVERSE); if (rl_point == rl_end) { waddch(input_window, ' '); wattroff(input_window, A_REVERSE); } else { waddch(input_window, rl_line_buffer[rl_point]); wattroff(input_window, A_REVERSE); waddnstr(input_window, rl_line_buffer + rl_point + 1, rl_end - rl_point); } wclrtoeol(input_window); wnoutrefresh(input_window); } void tui_refresh(void) { if (do_fullredraw) { do_fullredraw = false; full_redraw(); return; } last_entry_line_index = entry_line_index; while (entry_line_index < entry_lines) { wmove(entry_window, (entry_lines-1)-entry_line_index++, 0); wclrtoeol(entry_window); } entry_line_index = 0; update_input(); wnoutrefresh(entry_window); wnoutrefresh(main_window); doupdate(); } void tui_take_input(void) { extern bool do_run; const size_t paging_size = entry_lines / 2; input = wgetch(stdscr); switch (input) { case KEY_UP: case CTRL('p'): case CTRL('k'): { if (selection_relative == last_entry_line_index-1 && entry_lines != last_entry_line_index) { break; } if (selection_relative != entry_lines-1) { ++selection_relative; do_redisplay = true; } else { ++selection_offset; is_input_changed = true; } } break; case KEY_DOWN: case CTRL('n'): case CTRL('j'): { if (selection_relative != 0) { --selection_relative; do_redisplay = true; } else { if (selection_offset != 0) { --selection_offset; is_input_changed = true; } } } break; case KEY_PPAGE: case CTRL('u'): { selection_offset += paging_size; is_input_changed = true; } break; case KEY_NPAGE: case CTRL('d'): { if (selection_offset == 0) { selection_relative = 0; do_redisplay = true; } else if (selection_offset > paging_size) { selection_offset -= paging_size; is_input_changed = true; } else { selection_offset = 0; is_input_changed = true; } } break; case CTRL('q'): { do_execute = false; do_run = false; } break; case KEY_LEFT: { if (rl_point != 0) { --rl_point; is_input_changed = true; } } break; case KEY_RIGHT: { if (rl_point != rl_end) { ++rl_point; is_input_changed = true; } } break; case '\r': { do_run = false; } break; case ERR: break; default: { input_available = true; rl_callback_read_char(); is_input_changed = true; } break; } if (is_input_changed) { do_redisplay = true; } }