diff --git a/.gdb_history b/.gdb_history index 4b86e07..d87b145 100644 --- a/.gdb_history +++ b/.gdb_history @@ -11,3 +11,31 @@ p argv[0] p * argv[0] p (char *)argv[0] p argc +r +where +frame 5 +l +r +where +frame 5 +r +where +r +where +frame 3 +l +frame 2 +l +start +l +start +n +n +s +n +where +c +where +frame 1 +frame 2 +p stmt diff --git a/Makefile b/Makefile index 15ae7f6..a05ce41 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ .PHONY: clean run ifeq (${DEBUG}, 1) - LFLAGS += --debug --trace CXXFLAGS += -DDEBUG -O0 -ggdb -fno-inline WRAP := valgrind --track-origins=yes --leak-check=full --show-leak-kinds=all else @@ -15,7 +14,7 @@ LINKasd += $$(pkgconf --libs ncurses readline sqlite3) OBJECT.d:=object/ SOURCE.d:=source/ -SOURCE:=bash_history.yy.cpp main.cpp tui.cpp storage.cpp damerau_levenshtein.cpp +SOURCE:=bash_history.yy.cpp main.cpp cli.cpp tui.cpp storage.cpp damerau_levenshtein.cpp OBJECT:=$(addprefix ${OBJECT.d},$(addsuffix .o,$(basename ${SOURCE}))) SOURCE:=$(addprefix ${SOURCE.d},${SOURCE}) @@ -25,7 +24,7 @@ ${OUTPUT}: ${OBJECT} ${LINK.cpp} ${OBJECT} ${LINKasd} -o ${OUTPUT} object/%.yy.cpp: source/%.l - ${LEX} ${LFLAGS} --prefix=$*_ --header-file=$(basename $@).hpp -o $@ $< + ${LEX} --prefix=$*_ --header-file=$(basename $@).hpp -o $@ $< object/%.o: object/%.l.cpp ${COMPILE.cpp} $< -o $@ diff --git a/source/bash_history.l b/source/bash_history.l index fef65bb..ac2ea91 100644 --- a/source/bash_history.l +++ b/source/bash_history.l @@ -2,13 +2,12 @@ #include "storage.hpp" long timestamp; %} +%option nodefault %option noyywrap +%option nounput %% -\#[[:digit:]]+ { - timestamp = strtoll(yytext+1, NULL, 10); +\#[[:digit:]]+ { timestamp = strtoll(yytext+1, NULL, 10); } -[^\#\n].* { - insert_entry(timestamp, yytext); -} -\n { ; } +.* { insert_entry(timestamp, yytext); } +\n { ; } %% diff --git a/source/cli.cpp b/source/cli.cpp index 2f7c1b4..94e105a 100644 --- a/source/cli.cpp +++ b/source/cli.cpp @@ -1,28 +1,40 @@ +#include "cli.hpp" + #include #include #include -#include -#include using namespace std; [[ noreturn ]] void version() { - puts("Histui " - #include "version.inc" + puts( + # include "version.inc" ); exit(0); } [[ noreturn ]] -void usage(int exit_value = 0) { +void usage(int exit_value) { // TODO + puts( + "histui [options] \n" + "\tOptions:\n" + "\t\t-v --version\n" + "\t\t-h --help\n" + "\tVerbs:\n" + "\t\tenable : print a bash script to enable histui in the current shell\n" + "\t\ttui : run histui normally\n" + ); exit(exit_value); } -void global_options(const int argc, const char * const * const argv) { - for(int i = 0; i < argc; i++) { +void parse_global_options(const int argc, const char * const * const argv) { + for(int i = 1; i < argc; i++) { + if (argv[i][0] != '-') { + return; + } if (not strcmp(argv[i], "-v") || not strcmp(argv[i], "--version")) { version(); @@ -34,9 +46,18 @@ void global_options(const int argc, const char * const * const argv) { } } -typedef signed (*mainlike_t)(int argc, char * * argv); -map verb_table = { - {"tui", tui_main}, - {"import", import_main}, - {"export", export_main}, -}; +verb_t get_verb(const int argc, const char * const * const argv) { + for(int i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + continue; + } + if (not strcmp(argv[i], "tui")) { + return TUI; + } + if (not strcmp(argv[i], "enable")) { + return ENABLE; + } + return ERROR; + } + return ERROR; +} diff --git a/source/cli.hpp b/source/cli.hpp new file mode 100644 index 0000000..9b18e11 --- /dev/null +++ b/source/cli.hpp @@ -0,0 +1,11 @@ +#pragma once + +typedef enum { + TUI, + ENABLE, + ERROR, +} verb_t; + +extern void parse_global_options(const int argc, const char * const * const argv); +extern verb_t get_verb(const int argc, const char * const * const argv); +[[ noreturn ]] void usage(int exit_value = 0); diff --git a/source/damerau_levenshtein.cpp b/source/damerau_levenshtein.cpp index 31661f4..c13c2aa 100644 --- a/source/damerau_levenshtein.cpp +++ b/source/damerau_levenshtein.cpp @@ -65,7 +65,7 @@ damerau_levenshtein_( } /* - function to determine damerau-levenshtein distance + sqlite3 wrapper to determine damerau-levenshtein distance damerau_levenshtein(src,dts) => int */ void @@ -85,8 +85,28 @@ damerau_levenshtein( sqlite3_result_int(context, distance); } +// XXX +void +damerau_levenshtein_substring( + sqlite3_context *context, + [[maybe_unused]] int argc, + sqlite3_value **argv +){ + + const char *const s = (const char *)sqlite3_value_text(argv[0]); + const char *const t = (const char *)sqlite3_value_text(argv[1]); + int n = strlen(s); + int m = strlen(t); + n = (n < m ? n : m); + m = n; + + const int distance = damerau_levenshtein_(n, s, m, t); + + sqlite3_result_int(context, distance); +} + /* - function ensure damerau-levenshtein distance + sqlite wrapper to ensure damerau-levenshtein distance damerau_levenshtein(src,dts,max_distance) => bool */ void diff --git a/source/damerau_levenshtein.hpp b/source/damerau_levenshtein.hpp index 289ef07..8f267ef 100644 --- a/source/damerau_levenshtein.hpp +++ b/source/damerau_levenshtein.hpp @@ -1,3 +1,4 @@ #pragma once extern void damerau_levenshtein(sqlite3_context *context, int argc, sqlite3_value **argv); extern void is_damerau_levenshtein(sqlite3_context *context, int argc, sqlite3_value **argv); +extern void damerau_levenshtein_substring(sqlite3_context *context, int argc, sqlite3_value **argv); diff --git a/source/histui_enable.sh.inc b/source/histui_enable.sh.inc new file mode 100644 index 0000000..2f9e851 --- /dev/null +++ b/source/histui_enable.sh.inc @@ -0,0 +1,9 @@ +function _histui_run() { + COMMANDFILE="${XDG_CACHE_HOME}/histui_command.txt" + histui tui 3> "${COMMANDFILE}" + READLINE_LINE=$(cat "${COMMANDFILE}") + READLINE_POINT=${#READLINE_LINE} +} + +bind -x '"\e[A": _histui_run' +bind -x '"\C-r": _histui_run' diff --git a/source/main.cpp b/source/main.cpp index a3b609b..367bbef 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,39 +1,83 @@ #include +#include "cli.hpp" +#include "bash_history.yy.hpp" #include "storage.hpp" #include "tui.hpp" -#include "bash_history.yy.hpp" + +bool do_run = true; void init() { setlocale(LC_TIME, "C"); init_storage(); + bash_history_in = fopen("/home/anon/stow/.cache/.bash_history", "r"); + bash_history_lex(); init_tui(); } void deinit() { - deinit_storage(); deinit_tui(); + deinit_storage(); +} + +[[ noreturn ]] +void enable() { + // XXX one day... + /* + puts( + # embed "histui_enable.sh.inc" + ); + */ + puts( + R"delim( +function _histui_run() { + COMMANDFILE="${XDG_CACHE_HOME}/histui_command.txt" + histui tui 3> "${COMMANDFILE}" + READLINE_LINE=$(cat "${COMMANDFILE}") + READLINE_POINT=${#READLINE_LINE} +} + +bind -x '"\e[A": _histui_run' +bind -x '"\C-r": _histui_run' + )delim" + ); + exit(0); } signed main(int argc, char * argv[]) { - // TODO cli stuff + parse_global_options(argc, argv); + verb_t verb = get_verb(argc, argv); + switch (verb) { + case ENABLE: enable(); + case ERROR: usage(1); + case TUI: break; + } init(); - bash_history_in = fopen("/home/anon/stow/.cache/.bash_history", "r"); - bash_history_lex(); - tui_refresh(); entry_t entry; - while (true) { - query(rl_line_buffer); + while (do_run) { + if (is_input_changed) { + query(rl_line_buffer, entry_lines, selection_offset); + is_input_changed = false; + } else { + requery(); + } while (entry = get_entry(), entry.command != NULL) { tui_append_back(entry); } - tui_take_input(); tui_refresh(); + tui_take_input(); } + query(rl_line_buffer, 1, selection_offset + selection_relative); + int fd[2]; + pipe(fd); + dprintf(3, get_entry().command); + close(fd[0]); + close(fd[1]); + deinit(); return 0; diff --git a/source/storage.cpp b/source/storage.cpp index d6a0651..234cf56 100644 --- a/source/storage.cpp +++ b/source/storage.cpp @@ -5,14 +5,15 @@ #include "damerau_levenshtein.hpp" static sqlite3 * db; -static sqlite3_stmt * stmt; +static sqlite3_stmt * stmt = NULL; int init_storage(void) { sqlite3_open(":memory:", &db); + sqlite3_create_function(db, "damerau_levenshtein_substring", 2, SQLITE_ANY, 0, damerau_levenshtein_substring, NULL, NULL); sqlite3_create_function(db, "is_damerau_levenshtein", 3, SQLITE_ANY, 0, is_damerau_levenshtein, NULL, NULL); sqlite3_create_function(db, "damerau_levenshtein", 2, SQLITE_ANY, 0, damerau_levenshtein, NULL, NULL); - static const char * sql_create_table = "CREATE TABLE test (stamp INTEGER, data TEXT);"; + static const char * sql_create_table = "CREATE TABLE entries (stamp INTEGER, data TEXT);"; sqlite3_exec(db, sql_create_table, 0, 0, 0); return 0; @@ -25,7 +26,8 @@ int deinit_storage(void) { } int insert_entry(int timestamp, const char * const command) { - static const char * sql_insert = "INSERT INTO test (stamp, data) VALUES (?, ?);"; + static const char * sql_insert = "INSERT INTO entries (stamp, data) VALUES (?, ?);"; + sqlite3_stmt * stmt; sqlite3_prepare_v2(db, sql_insert, -1, &stmt, 0); sqlite3_bind_int64(stmt, 1, timestamp); @@ -36,14 +38,36 @@ int insert_entry(int timestamp, const char * const command) { return 0; } -void query(const char * const string) { - sqlite3_prepare_v2(db, "SELECT * FROM test ORDER BY DAMERAU_LEVENSHTEIN(data, ?) LIMIT 40;", -1, &stmt, 0); - sqlite3_bind_text(stmt, 1, string, -1, SQLITE_TRANSIENT); +void query(const char * const string, const size_t limit, const size_t offset) { + sqlite3_finalize(stmt); + const char * sql_query; + if (string[0] != '\0') { + sql_query = "SELECT * FROM entries " + "ORDER BY DAMERAU_LEVENSHTEIN_SUBSTRING(data, ?) " + "LIMIT ? " + "OFFSET ?;" + ; + sqlite3_prepare_v2(db, sql_query, -1, &stmt, 0); + sqlite3_bind_text(stmt, 1, string, -1, SQLITE_TRANSIENT); + sqlite3_bind_int64(stmt, 2, (long)limit); + sqlite3_bind_int64(stmt, 3, (long)offset); + } else { + sql_query = "SELECT * FROM entries " + "ORDER BY stamp DESC " + "LIMIT ? " + "OFFSET ?;"; + sqlite3_prepare_v2(db, sql_query, -1, &stmt, 0); + sqlite3_bind_int64(stmt, 1, (long)limit); + sqlite3_bind_int64(stmt, 2, (long)offset); + } +} + +void requery(void) { + sqlite3_reset(stmt); } entry_t get_entry(void) { if (sqlite3_step(stmt) != SQLITE_ROW) { - sqlite3_finalize(stmt); return (entry_t){ .timestamp = 0, .command = NULL diff --git a/source/storage.hpp b/source/storage.hpp index 42fded2..24740c0 100644 --- a/source/storage.hpp +++ b/source/storage.hpp @@ -1,9 +1,11 @@ #pragma once +#include #include "entry.h" int init_storage(void); int deinit_storage(void); int insert_entry(int timestamp, const char * const command); -void query(const char * const string); +void query(const char * const string, const size_t limit, const size_t offset); +void requery(void); entry_t get_entry(void); diff --git a/source/tui.cpp b/source/tui.cpp index 21c7926..97e5f7b 100644 --- a/source/tui.cpp +++ b/source/tui.cpp @@ -5,31 +5,54 @@ #include #include -// XXX -#include +#include + +extern bool do_run; + +size_t entry_lines; +bool is_input_changed = true; static WINDOW * main_window; static WINDOW * entry_window; static WINDOW * input_window; +static WINDOW * version_window; static int input_available = false; -static char input; +static int input; + +size_t selection_offset = 0; +size_t selection_relative = 0; +static size_t entry_line_index = 0; + +static char version_string[] = +# include "version.inc" +; static void refresh_input(void); int init_tui(void) { // Ncurses initscr(); + nonl(); + cbreak(); noecho(); curs_set(0); + keypad(stdscr, TRUE); - main_window = newwin(LINES-1, COLS, 0, 0); - entry_window = subwin(main_window, LINES-3, COLS-2, 1, 1); - input_window = newwin(1, COLS, LINES-1, 0); + entry_lines = LINES-3; + + main_window = newwin(LINES-1, COLS, 0, 0); + entry_window = subwin(main_window, entry_lines, COLS-2, 1, 1); + version_window = subwin(main_window, 1, strlen(version_string), 0, 5); + input_window = newwin(1, COLS, LINES-1, 0); refresh(); box(main_window, 0, 0); + // XXX + waddstr(version_window, version_string); + wrefresh(version_window); + // Readline rl_bind_key('\t', rl_insert); rl_catch_signals = 0; @@ -69,13 +92,23 @@ void tui_append_back(const entry_t entry) { char time_buffer[TIME_BUFFER_SIZE]; strftime(time_buffer, TIME_BUFFER_SIZE, "%Y-%m-%d %H:%M:%S", tm_info); - wprintw(entry_window, "%s %s\n", - time_buffer, - entry.command + if (entry_line_index == selection_relative) { + wattron(entry_window, A_REVERSE); + } + mvwprintw(entry_window, (entry_lines-1)-entry_line_index, 0, + "%s %s\n", + time_buffer, + entry.command ); + if (entry_line_index == selection_relative) { + wattroff(entry_window, A_REVERSE); + } + + ++entry_line_index; } static void refresh_input(void) { + entry_line_index = 0; wmove(input_window, 0, 0); wclrtoeol(input_window); waddstr(input_window, "$ "); @@ -94,6 +127,34 @@ void tui_refresh(void) { void tui_take_input(void) { input = wgetch(stdscr); - input_available = true; - rl_callback_read_char(); + switch (input) { + case CTRL('p'): + case CTRL('k'): { + if (selection_relative != entry_lines-1) { + ++selection_relative; + } else { + ++selection_offset; + is_input_changed = true; + } + } break; + case CTRL('n'): + case CTRL('j'): { + if (selection_relative != 0) { + --selection_relative; + } else { + if (selection_offset != 0) { + --selection_offset; + is_input_changed = true; + } + } + } break; + case '\r': { + do_run = false; + } break; + default: { + input_available = true; + rl_callback_read_char(); + is_input_changed = true; + } break; + } } diff --git a/source/tui.hpp b/source/tui.hpp index a02e6a4..6162a38 100644 --- a/source/tui.hpp +++ b/source/tui.hpp @@ -1,8 +1,14 @@ #pragma once +#include #include "entry.h" extern "C" char * rl_line_buffer; +extern size_t entry_lines; +extern size_t selection_offset; +extern size_t selection_relative; +extern bool is_input_changed; + int init_tui(void); int deinit_tui(void); void tui_refresh(void); diff --git a/source/version.inc b/source/version.inc index e3c63c6..1c05bc3 100644 --- a/source/version.inc +++ b/source/version.inc @@ -1 +1 @@ -"0.2" +"Histui v0.2"