From 058f196456cf1adb6b7ba5bdd1235dc082ae7071 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=A0t=C4=9Bp=C3=A1n=20Bal=C3=A1=C5=BEik?= Date: Wed, 25 Jan 2017 16:47:49 +0100 Subject: [PATCH] kresc: improve libedit/editline based history and tab-completion --- daemon/daemon.mk | 2 +- daemon/kresc.c | 303 +++++++++++++++++++++++++++++++++++++---------- daemon/main.c | 2 + 3 files changed, 246 insertions(+), 61 deletions(-) diff --git a/daemon/daemon.mk b/daemon/daemon.mk index 8fe117f1b..5653ffa35 100644 --- a/daemon/daemon.mk +++ b/daemon/daemon.mk @@ -62,7 +62,7 @@ daemon/lua/kres-gen.lua: | $(libkres) ifeq ($(HAS_libedit), yes) kresc_SOURCES := daemon/kresc.c kresc_CFLAGS += -fPIE $(libedit_CFLAGS) -kresc_LIBS += $(libedit_LIBS) +kresc_LIBS += $(contrib_TARGET) $(libedit_LIBS) kresc_DEPEND := $(libkres) $(contrib) $(eval $(call make_sbin,kresc,daemon,yes)) client: $(kresc) diff --git a/daemon/kresc.c b/daemon/kresc.c index b56959e2e..5aebeae60 100644 --- a/daemon/kresc.c +++ b/daemon/kresc.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2016 CZ.NIC, z.s.p.o. +/* Copyright (C) 2016-2017 CZ.NIC, z.s.p.o. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,7 +14,9 @@ along with this program. If not, see . */ #include +#include #include +#include #include #include #include @@ -22,17 +24,18 @@ #include #include #include +#include #include #include -#define HISTORY_FILE ".kresc_history" +#define HISTORY_FILE "kresc_history" #define PROGRAM_NAME "kresc" FILE *g_tty = NULL; //!< connection to the daemon -static char *run_cmd(const char *cmd, uint32_t * msg_len); +static char *run_cmd(const char *cmd, size_t * msg_len); -char *prompt(EditLine * e) +const char *prompt(EditLine * e) { return PROGRAM_NAME "> "; } @@ -44,67 +47,216 @@ bool starts_with(const char *a, const char *b) return 0; } +//! Returns Lua name of type of value, NULL on error. Puts length of type in name_len; +const char *get_type_name(const char *value) +{ + if (value == NULL) { + return NULL; + } + + for (int i = 0; value[i]; i++) { + if (value[i] == ')') { + //Return NULL to prevent unexpected function call + return NULL; + } + } + + char *cmd = afmt("type(%s)", value); + + if (!cmd) { + perror("While tab-completing."); + return NULL; + } + + size_t name_len; + char *type = run_cmd(cmd, &name_len); + if (!type) { + return NULL; + } else { + free(cmd); + } + + if (starts_with(type, "[")) { + //Return "nil" on non-valid name. + return "nil"; + } else { + type[(strlen(type)) - 1] = '\0'; + return type; + } +} + static unsigned char complete(EditLine * el, int ch) { int argc, pos; const char **argv; const LineInfo *li = el_line(el); Tokenizer *tok = tok_init(NULL); - // Parse the line. - int ret = tok_line(tok, li, &argc, &argv, NULL, &pos); - uint32_t msg_len; + //Tokenize current line. + int ret = tok_line(tok, li, &argc, &argv, NULL, &pos); - char *help = run_cmd("help()", &msg_len); - if (!help) { - perror("While communication with daemon"); + if (ret != 0) { + perror("While tab-completing."); + goto complete_exit; + } + //Show help. + if (argc == 0) { + size_t help_len; + char *help = run_cmd("help()", &help_len); + if (help) { + printf("\n%s", help); + free(help); + } else { + perror("While communication with daemon"); + } goto complete_exit; } - if (ret != 0) { + if (argc > 1) { goto complete_exit; } + //Get name of type of current line. + const char *type = get_type_name(argv[0]); - if (argc == 0) { - printf("\n%s", help); - } - - char *lines; - lines = strtok(help, "\n"); - int matches = 0; - bool exactmatch = 0; - char *lastmatch; - int i = 0; - while (lines != NULL) { - if (!(i % 2)) - if (argv[0] && starts_with(lines, argv[0])) { - printf("\n%s", lines); - lastmatch = lines; + if (!type) { + goto complete_exit; + } + //Get position of last dot in current line (useful for parsing table). + char *dot = strrchr(argv[0], '.'); + + //Line is not a name of some table and there is no dot in it. + if (strncmp(type, "table", 5) && !dot) { + //Parse Lua globals. + size_t globals_len; + char *globals = run_cmd("_G.__orig_name_list", &globals_len); + if (!globals) { + perror("While tab-completing"); + goto complete_exit; + } + //Show possible globals. + char *globals_tok = strdup(globals); + free(globals); + if (!globals_tok) { + goto complete_exit; + } + char *token = strtok(globals_tok, "\n"); + int matches = 0; + char *lastmatch; + while (token) { + if (argv[0] && starts_with(token, argv[0])) { + printf("\n%s (%s)", token, + get_type_name(token)); + fflush(stdout); + lastmatch = token; matches++; - if (!strcmp(lines, argv[0])) - exactmatch = 1; } - lines = strtok(NULL, "\n"); - i++; - } - printf("\n"); - if (matches == 1) { - char *brace = strchr(lastmatch, '('); - if (brace != NULL) - *(brace + 1) = '\0'; - el_deletestr(el, pos); - el_insertstr(el, lastmatch); - pos = strlen(lastmatch); - if (exactmatch && brace == NULL) { - char *prettyprint = run_cmd(lastmatch, &msg_len); - printf("%s", prettyprint); + token = strtok(NULL, "\n"); + } + if (matches > 1) { + printf("\n"); + } + //Complete matching global. + if (matches == 1) { + el_deletestr(el, pos); + el_insertstr(el, lastmatch); + pos = strlen(lastmatch); + } + free(globals_tok); + + //Current line (or part of it) is a name of some table. + } else if ((dot && !strncmp(type, "nil", 3)) + || !strncmp(type, "table", 5)) { + char *table = strdup(argv[0]); + if (!table) { + perror("While tab-completing"); + goto complete_exit; + } + //Get only the table name (without partial member name). + if (dot) { + *(table + (dot - argv[0])) = '\0'; + } + //Insert a dot after the table name. + if (!strncmp(type, "table", 5)) { el_insertstr(el, "."); - free(prettyprint); + pos++; } + //Check if the substring before dot is a valid table name. + const char *t_type = get_type_name(table); + if (t_type && !strncmp("table", t_type, 5)) { + //Get string of members of the table. + char *cmd = + afmt + ("do local s=\"\"; for i in pairs(%s) do s=s..i..\"\\n\" end return(s) end", + table); + if (!cmd) { + perror("While tab-completing."); + goto complete_exit; + } + size_t members_len; + char *members = run_cmd(cmd, &members_len); + free(cmd); + if (!members) { + perror("While communication with daemon"); + } + //Split members by newline. + char *members_tok = strdup(members); + free(members); + if (!members_tok) { + goto complete_exit; + } + char *token = strtok(members_tok, "\n"); + int matches = 0; + char *lastmatch; + if (!dot || dot - argv[0] + 1 == strlen(argv[0])) { + //Prints all members. + while (token) { + char *member = + afmt("%s.%s", table, token); + printf("\n%s (%s)", member, + get_type_name(member)); + token = strtok(NULL, "\n"); + matches++; + } + } else { + //Print members matching the current line. + while (token) { + if (argv[0] + && starts_with(token, dot + 1)) { + printf("\n%s.%s (%s)", table, + token, + get_type_name(afmt + ("%s.%s", + table, + token))); + lastmatch = token; + matches++; + } + token = strtok(NULL, "\n"); + } + + //Complete matching member. + if (matches == 1) { + el_deletestr(el, pos); + el_insertstr(el, table); + el_insertstr(el, "."); + el_insertstr(el, lastmatch); + pos = + strlen(lastmatch) + strlen(table) + + 1; + } + } + if (matches > 1) { + printf("\n"); + } + free(members_tok); + } + } else if (!strncmp(type, "function", 8)) { + //Add left parenthesis to function name. + el_insertstr(el, "("); + pos++; } complete_exit: - free(help); tok_reset(tok); tok_end(tok); return CC_REDISPLAY; @@ -133,7 +285,6 @@ static int init_tty(const char *path) g_tty = fdopen(fd, "r+"); if (!g_tty) { perror("While opening TTY"); - return 1; } // Switch to binary mode and consume the text "> ". @@ -146,15 +297,13 @@ static int init_tty(const char *path) return 0; } -//! Run a command on the daemon; return the answer or NULL on failure. -static char *run_cmd(const char *cmd, uint32_t * msg_len) +//! Run a command on the daemon; return the answer or NULL on failure, puts answer length to out_len. +static char *run_cmd(const char *cmd, size_t * out_len) { if (!g_tty || !cmd) { assert(false); return NULL; } - printf("cmd: %s\n", cmd); - if (fprintf(g_tty, "%s", cmd) < 0 || fflush(g_tty)) return NULL; uint32_t len; @@ -168,21 +317,19 @@ static char *run_cmd(const char *cmd, uint32_t * msg_len) return NULL; } msg[len] = '\0'; - *msg_len = len; + *out_len = len; return msg; } static int interact() { - EditLine *el; History *hist; int count; const char *line; - int keepreading = 1; HistEvent ev; el = el_init(PROGRAM_NAME, stdin, stdout, stderr); - el_set(el, EL_PROMPT, &prompt); + el_set(el, EL_PROMPT, prompt); el_set(el, EL_EDITOR, "emacs"); el_set(el, EL_ADDFN, PROGRAM_NAME "-complete", "Perform " PROGRAM_NAME " completion.", complete); @@ -196,14 +343,49 @@ static int interact() history(hist, &ev, H_SETSIZE, 800); el_set(el, EL_HIST, history, hist); - const char hist_file[] = HISTORY_FILE; - history(hist, &ev, H_LOAD, hist_file); + char *hist_file = NULL; + + char *data_home = getenv("XDG_DATA_HOME"); + + //Check whether $XDG_DATA_HOME is set. + if (!data_home || *data_home == '\0') { + const char *home = getenv("HOME"); //This should be set on any POSIX compliant OS, even for nobody + + //Create necessary folders. + char *dirs[3] = + { afmt("%s/.local", home), afmt("%s/.local/share", home), + afmt("%s/.local/share/kresd/", home) + }; + bool ok = true; + for (int i = 0; i < 3; i++) { + if (mkdir(dirs[i], 0755) && errno != EEXIST) { + ok = false; + break; + } + } + if (ok) { + hist_file = + afmt("%s/.local/share/kresd/" HISTORY_FILE, home); + } + } else { + if (!mkdir(afmt("%s/kresd/", data_home), 0755) + || errno == EEXIST) { + hist_file = afmt("%s/kresd/" HISTORY_FILE, data_home); + } + } + + //Load history file + if (hist_file) { + history(hist, &ev, H_LOAD, hist_file); + } else { + perror("While opening history file"); + } - while (keepreading) { + while (1) { line = el_gets(el, &count); if (count > 0) { history(hist, &ev, H_ENTER, line); - uint32_t msg_len; + size_t msg_len; char *msg = run_cmd(line, &msg_len); if (!msg) { perror("While communication with daemon"); @@ -216,13 +398,14 @@ static int interact() if (msg_len == 0 || msg[msg_len - 1] != '\n') { printf("\n"); } - printf("%d\n", msg_len); - history(hist, &ev, H_SAVE, hist_file); - + if (hist_file) { + history(hist, &ev, H_SAVE, hist_file); + } free(msg); } } history_end(hist); + free(hist_file); el_end(el); if (feof(stdin)) return 0; diff --git a/daemon/main.c b/daemon/main.c index bc3d42700..fb60f30d5 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -93,6 +93,8 @@ static void tty_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) char *cmd = buf->base; if (cmd[nread - 1] == '\n') { cmd[nread - 1] = '\0'; + } else { + cmd[nread] = '\0'; } /* Pseudo-command for switching to "binary output" */ -- 2.47.3