From a1f5f569bc8113555c75693ce84bbb3874181cbb Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 9 Jan 2020 22:17:48 -0500 Subject: [PATCH] Write command history to file --- include/history.h | 20 +++++++ include/interactive.h | 3 + meson.build | 3 +- src/history.c | 136 ++++++++++++++++++++++++++++++++++++++++++ src/main.c | 14 ++++- 5 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 include/history.h create mode 100644 src/history.c diff --git a/include/history.h b/include/history.h new file mode 100644 index 0000000..482aa21 --- /dev/null +++ b/include/history.h @@ -0,0 +1,20 @@ +#ifndef _MRSH_HISTORY_H +#define _MRSH_HISTORY_H + +struct imrsh_history_entry { + char *cmd; + struct imrsh_history_entry *next; +}; + +/* XXX: linked list is dumb for this, will be slow */ +struct imrsh_history { + FILE *file; + struct imrsh_history_entry *entries; + struct imrsh_history_entry *tail; +}; + +void imrsh_history_load(struct imrsh_history *history); +void imrsh_history_finish(struct imrsh_history *history); +void imrsh_history_append(struct imrsh_history *history, const char *cmd); + +#endif diff --git a/include/interactive.h b/include/interactive.h index 71871d9..5a30862 100644 --- a/include/interactive.h +++ b/include/interactive.h @@ -5,8 +5,11 @@ #include #include +struct imrsh_history; + struct imrsh_interactive { const char *prompt; + const struct imrsh_history *history; struct mrsh_buffer *read_buffer; struct mrsh_state *state; bool commit; diff --git a/meson.build b/meson.build index 2e6d782..a7f5947 100644 --- a/meson.build +++ b/meson.build @@ -42,8 +42,9 @@ imrsh_inc = include_directories('include') executable( 'imrsh', files( - 'src/main.c', + 'src/history.c', 'src/interactive.c', + 'src/main.c', ), include_directories: [imrsh_inc], dependencies: [mrsh, tickit], diff --git a/src/history.c b/src/history.c new file mode 100644 index 0000000..a92bf1a --- /dev/null +++ b/src/history.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "history.h" + +static void +posix_dirname(char *path, char *dname) +{ + char p[PATH_MAX+1]; + char *t; + + assert(strlen(path) <= PATH_MAX); + + strcpy(p, path); + t = dirname(path); + strcpy(dname, t); + + /* restore the path if dirname worked in-place */ + if (t == path && path != dname) { + strcpy(path, p); + } +} + +static int +mkdirs(char *path, mode_t mode) +{ + char dname[PATH_MAX + 1]; + posix_dirname(path, dname); + if (strcmp(dname, "/") == 0) { + return 0; + } + if (mkdirs(dname, mode) != 0) { + return -1; + } + if (mkdir(path, mode) != 0 && errno != EEXIST) { + return -1; + } + errno = 0; + return 0; +} + +void +imrsh_history_load(struct imrsh_history *history) +{ + char *paths[] = { + "$XDG_DATA_HOME/imrsh/history", + "$HOME/.local/share/imrsh/history", + }; + FILE *f = NULL; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { + wordexp_t p; + if (wordexp(paths[i], &p, WRDE_UNDEF) == 0) { + char *path = p.we_wordv[0]; + char dirname[PATH_MAX + 1]; + posix_dirname(path, dirname); + if (mkdirs(dirname, 0755) != 0) { + fprintf(stderr, "mkdirs: error: %s\n", + strerror(errno)); + return; + } + if ((f = fopen(path, "a+"))) { + fseek(f, 0, SEEK_SET); + wordfree(&p); + break; + } + wordfree(&p); + } + } + if (!f) { + fprintf(stderr, "error: failed to a suitable history file\n"); + return; + } + size_t n = 0; + char *line = NULL; + struct imrsh_history_entry **next = &history->entries; + struct imrsh_history_entry *tail = NULL; + while (getline(&line, &n, f) != -1) { + for (char *s = line; *s; ++s) { + if (*s == '\r') { + *s = '\n'; + } + } + *next = tail = calloc(1, sizeof(struct imrsh_history_entry)); + tail->cmd = strdup(line); + next = &tail->next; + } + history->tail = tail; + history->file = f; +} + +void +imrsh_history_finish(struct imrsh_history *history) +{ + if (history->file != NULL) { + fclose(history->file); + } + struct imrsh_history_entry *ent = history->entries; + while (ent) { + struct imrsh_history_entry *next = ent->next; + free(ent->cmd); + free(ent); + ent = next; + } +} + +void +imrsh_history_append(struct imrsh_history *history, const char *cmd) +{ + struct imrsh_history_entry *next = + calloc(1, sizeof(struct imrsh_history_entry)); + next->cmd = strdup(cmd); + if (history->tail) { + history->tail->next = next; + } else { + history->entries = next; + } + history->tail = next; + if (history->file) { + fseek(history->file, 0, SEEK_END); + for (int i = 0; cmd[i]; ++i) { + if (cmd[i] == '\n') { + fputc('\r', history->file); + } else { + fputc(cmd[i], history->file); + } + } + fputc('\n', history->file); + fflush(history->file); + } +} diff --git a/src/main.c b/src/main.c index 4c6ee8f..f1d627a 100644 --- a/src/main.c +++ b/src/main.c @@ -10,6 +10,7 @@ #include #include #include +#include "history.h" #include "interactive.h" extern char **environ; @@ -44,8 +45,10 @@ main(int argc, char *argv[]) { struct mrsh_state *state = mrsh_state_create(); assert(state); + struct imrsh_history history = { 0 }; struct mrsh_buffer read_buffer = { 0 }; struct imrsh_interactive istate = { + .history = &history, .read_buffer = &read_buffer, .state = state, }; @@ -56,6 +59,10 @@ main(int argc, char *argv[]) return 1; } + if (!(state->options & MRSH_OPT_NOLOG)) { + imrsh_history_load(&history); + } + if (!mrsh_populate_env(state, environ)) { return 1; } @@ -141,11 +148,16 @@ main(int argc, char *argv[]) } else { mrsh_run_program(state, prog); mrsh_destroy_terminated_jobs(state); + if (!(state->options & MRSH_OPT_NOLOG)) { + imrsh_history_append(&history, + read_buffer.data); + } } mrsh_buffer_finish(&read_buffer); } mrsh_program_destroy(prog); } - return 0; + imrsh_history_finish(&history); + return state->exit; } -- 2.45.2