From e9a11deef01346dc75728f4debefcc694254e5f0 Mon Sep 17 00:00:00 2001
From: Benji Dial <benji6283@gmail.com>
Date: Tue, 2 Mar 2021 20:40:10 -0500
Subject: [PATCH] command history in shell

---
 src/user/highway/main.c             |   4 +-
 src/user/include/libterm/readline.h |   8 +-
 src/user/libterm/readline.c         | 122 +++++++++++++++++++++++++++-
 3 files changed, 129 insertions(+), 5 deletions(-)

diff --git a/src/user/highway/main.c b/src/user/highway/main.c
index 47f3985..bcc3e01 100644
--- a/src/user/highway/main.c
+++ b/src/user/highway/main.c
@@ -14,9 +14,11 @@ void main(const char *arg) {
   term_add_sz("Portland Highway\nType \"help\" for help.\n");
   term_paint();
 
+  struct history *h = new_history(200);
+
   char cmd_buf[128];
   while (1) {
-    read_line(cmd_buf, 127, "> ");
+    read_line(cmd_buf, 127, "> ", h);
     run_line(cmd_buf);
   }
 }
\ No newline at end of file
diff --git a/src/user/include/libterm/readline.h b/src/user/include/libterm/readline.h
index b1d5b0a..fdfb408 100644
--- a/src/user/include/libterm/readline.h
+++ b/src/user/include/libterm/readline.h
@@ -3,8 +3,14 @@
 
 #include <stdint.h>
 
+struct history;
+
+//returns zero if memory allocation failed or if max_entries was zero or one
+struct history *new_history(uint32_t max_entries);
+
 //returns length of string without null terminator
 //max_length doesn't include null terminator
-uint32_t read_line(char *sz, uint32_t max_length, const char *prompt);
+//pass null pointer in hist for no history
+uint32_t read_line(char *sz, uint32_t max_length, const char *prompt, struct history *hist);
 
 #endif
\ No newline at end of file
diff --git a/src/user/libterm/readline.c b/src/user/libterm/readline.c
index fdb61a1..c59a60b 100644
--- a/src/user/libterm/readline.c
+++ b/src/user/libterm/readline.c
@@ -1,13 +1,82 @@
 #include <libterm/terminal.h>
 
 #include <knob/block.h>
+#include <knob/heap.h>
 #include <knob/key.h>
 
 #include <stdint.h>
 
-//returns length of string without null terminator
-//max_length doesn't include null terminator
-uint32_t read_line(char *sz, uint32_t max_length, const char *prompt) {
+struct he {
+  struct he *prev;
+  struct he *next;
+  const char *data;
+};
+
+struct history {
+  uint32_t entries_left;
+  struct he *newest_entry;
+  struct he *oldest_entry;
+};
+
+static void move_entry_to_front(struct history *h, struct he *e) {
+  if (!e || !h || (e == h->newest_entry))
+    return;
+  if (e->prev)
+    e->prev->next = e->next;
+  if (e->next)
+    e->next->prev = e->prev;
+  if (e == h->oldest_entry)
+    h->oldest_entry = e->next;
+  e->prev = h->newest_entry;
+  e->next = 0;
+  if (h->newest_entry)
+    h->newest_entry->next = e;
+  h->newest_entry = e;
+}
+
+static void add_to_history(struct history *h, const char *from) {
+  const uint32_t from_size = strlen(from) + 1;
+
+  char *const copy = get_block(from_size);
+  blockcpy(copy, from, from_size);
+
+  struct he *entry;
+
+  if (h->entries_left) {
+    --h->entries_left;
+    entry = get_block(sizeof(struct he));
+  }
+
+  else {
+    entry = h->oldest_entry;
+    h->oldest_entry = entry->next;
+    entry->next->prev = 0;
+    free_block(entry->data);
+  }
+
+  entry->data = copy;
+  entry->prev = h->newest_entry;
+  entry->next = 0;
+  if (h->newest_entry)
+    h->newest_entry->next = entry;
+  else
+    h->oldest_entry = entry;
+  h->newest_entry = entry;
+}
+
+struct history *new_history(uint32_t max_entries) {
+  if (max_entries < 2)
+    return 0;
+  struct history *h = get_block(sizeof(struct history));
+  if (!h)
+    return 0;
+  h->newest_entry = 0;
+  h->oldest_entry = 0;
+  h->entries_left = max_entries;
+  return h;
+}
+
+uint32_t read_line(char *sz, uint32_t max_length, const char *prompt, struct history *hist) {
   term_add_sz(prompt);
   term_add_char(' ');
   term_paint();
@@ -15,6 +84,9 @@ uint32_t read_line(char *sz, uint32_t max_length, const char *prompt) {
   uint32_t l = 0;
   uint32_t c = 0;
 
+  struct he *h_on = 0;
+  bool he_modified = false;
+
   while (1) {
     struct key_packet kp = term_get_key_blocking();
     switch (kp.key_id) {
@@ -54,6 +126,7 @@ uint32_t read_line(char *sz, uint32_t max_length, const char *prompt) {
     case KEY_BSPACE:
       if (!c)
         continue;
+      he_modified = true;
       --c;
       --l;
       for (uint32_t i = c; i < l; ++i)
@@ -71,16 +144,59 @@ uint32_t read_line(char *sz, uint32_t max_length, const char *prompt) {
         term_cursor_right();
       }
       sz[l] = '\0';
+      if (hist) {
+        if (he_modified)
+          add_to_history(hist, sz);
+        else if (h_on)
+          move_entry_to_front(hist, h_on);
+      }
       term_add_char('\n');
       term_paint();
       _yield_task();
       return l;
+    case KEY_UP_ARROW:
+      if (!hist || !hist->newest_entry || (h_on == hist->oldest_entry))
+        continue;
+      he_modified = false;
+      h_on = h_on ? h_on->prev : hist->newest_entry;
+      for (uint32_t i = 0; i < c; ++i)
+        term_cursor_left();
+      for (uint32_t i = 0; i < l; ++i)
+        term_add_char(' ');
+      for (uint32_t i = 0; i < l; ++i)
+        term_cursor_left();
+      l = strcpy(sz, h_on->data);
+      term_add_sn_no_ww(sz, l);
+      c = l;
+      term_paint();
+      continue;
+    case KEY_DOWN_ARROW:
+      if (!h_on)
+        continue;
+      he_modified = false;
+      h_on = h_on->next;
+      for (uint32_t i = 0; i < c; ++i)
+        term_cursor_left();
+      for (uint32_t i = 0; i < l; ++i)
+        term_add_char(' ');
+      for (uint32_t i = 0; i < l; ++i)
+        term_cursor_left();
+      if (h_on) {
+        l = strcpy(sz, h_on->data);
+        term_add_sn_no_ww(sz, l);
+      }
+      else
+        l = 0;
+      c = l;
+      term_paint();
+      continue;
     default:
       if (l == max_length)
         continue;
       char ch = key_to_char(kp);
       if (!ch)
         continue;
+      he_modified = true;
       if (c == l) {
         ++l;
         term_add_char(sz[c++] = ch);