Added history and color to riscv debug cli

PiperOrigin-RevId: 903365115
Change-Id: If20c32ff42c69252472de3abaa2bbeb871b0a99c
diff --git a/MODULE.bazel b/MODULE.bazel
index 147be0f..822ae87 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -53,6 +53,10 @@
     name = "rules_cc",
     version = "0.1.3",
 )
+bazel_dep(
+    name = "linenoise",
+    version = "2.0.0",
+)
 
 sim_deps = use_extension(
     "@mpact-sim//:extensions.bzl",
diff --git a/riscv/BUILD b/riscv/BUILD
index cb65100..961213f 100644
--- a/riscv/BUILD
+++ b/riscv/BUILD
@@ -1241,6 +1241,7 @@
         "@abseil-cpp//absl/strings",
         "@abseil-cpp//absl/strings:str_format",
         "@com_googlesource_code_re2//:re2",
+        "@linenoise",
         "@mpact-sim//mpact/sim/generic:core",
         "@mpact-sim//mpact/sim/generic:core_debug_interface",
         "@mpact-sim//mpact/sim/generic:debug_command_shell_interface",
diff --git a/riscv/debug_command_shell.cc b/riscv/debug_command_shell.cc
index 2e32fae..36ae310 100644
--- a/riscv/debug_command_shell.cc
+++ b/riscv/debug_command_shell.cc
@@ -15,6 +15,7 @@
 #include "riscv/debug_command_shell.h"
 
 #include <cstdint>
+#include <cstdlib>
 #include <cstring>
 #include <fstream>
 #include <istream>
@@ -31,6 +32,7 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
 #include "absl/strings/string_view.h"
+#include "linenoise.h"
 #include "mpact/sim/generic/core_debug_interface.h"
 #include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/type_helpers.h"
@@ -186,45 +188,49 @@
   std::string previous_line;
   current_core_ = 0;
   absl::string_view line_view;
+  linenoiseHistorySetMaxLen(1000);
   while (true) {
     // Prompt and read in the next command.
     auto pc_result =
         core_access_[current_core_].debug_interface->ReadRegister("pc");
-    std::string prompt;
+    std::string pre_prompt;
     auto result =
         core_access_[current_core_].debug_interface->GetLastHaltReason();
     if (result.ok()) {
       switch (result.value()) {
         case *HaltReason::kSoftwareBreakpoint:
-          absl::StrAppend(&prompt, "Stopped at software breakpoint\n");
+          absl::StrAppend(&pre_prompt, "Stopped at software breakpoint\n");
           break;
         case *HaltReason::kUserRequest:
-          absl::StrAppend(&prompt, "Stopped at user request\n");
+          absl::StrAppend(&pre_prompt, "Stopped at user request\n");
           break;
         case *HaltReason::kDataWatchPoint:
-          absl::StrAppend(&prompt, "Stopped at data watchpoint\n");
+          absl::StrAppend(&pre_prompt, "Stopped at data watchpoint\n");
           break;
         case *HaltReason::kProgramDone:
-          absl::StrAppend(&prompt, "Program done\n");
+          absl::StrAppend(&pre_prompt, "Program done\n");
           break;
         default:
           if ((result.value() >= *HaltReason::kUserSpecifiedMin) &&
               (result.value() <= *HaltReason::kUserSpecifiedMax)) {
-            absl::StrAppend(&prompt, "Stopped for custom halt reason\n");
+            absl::StrAppend(&pre_prompt, "Stopped for custom halt reason\n");
           }
           break;
       }
     }
+    std::string prompt;
     if (pc_result.ok()) {
       auto* loader = core_access_[current_core_].loader_getter();
       if (loader != nullptr) {
         auto fcn_result = loader->GetFunctionName(pc_result.value());
         if (fcn_result.ok()) {
-          absl::StrAppend(&prompt, "[", fcn_result.value(), "]:\n");
+          absl::StrAppend(&pre_prompt, kSymbolColor, "[", fcn_result.value(),
+                          "]:\n", kDefaultColor);
         }
         auto symbol_result = loader->GetFcnSymbolName(pc_result.value());
         if (symbol_result.ok()) {
-          absl::StrAppend(&prompt, symbol_result.value(), ":\n");
+          absl::StrAppend(&pre_prompt, kSymbolColor, symbol_result.value(),
+                          kDefaultColor, ":\n");
         }
       }
       absl::StrAppend(&prompt,
@@ -233,9 +239,10 @@
           core_access_[current_core_].debug_interface->GetDisassembly(
               pc_result.value());
       if (disasm_result.ok()) {
-        absl::StrAppend(&prompt, "   ", disasm_result.value());
+        absl::StrAppend(&pre_prompt, "   ", kTextColor, disasm_result.value(),
+                        kDefaultColor);
       }
-      absl::StrAppend(&prompt, "\n");
+      absl::StrAppend(&pre_prompt, "\n");
     }
     absl::StrAppend(&prompt, "[", current_core_, "] > ");
     while (!command_streams_.empty()) {
@@ -245,8 +252,18 @@
       // Read a command from the input stream. If it's from a file, then ignore
       // empty lines and comments.
       do {
-        if (command_streams_.size() == 1) os << prompt;
-        current_is.getline(line, kLineSize);
+        if (command_streams_.size() == 1) {
+          os << pre_prompt;
+          char* line_c = linenoise(prompt.c_str());
+          if (line_c != nullptr) {
+            absl::SNPrintF(line, kLineSize, "%s", line_c);
+            free(line_c);
+          } else {
+            line[0] = '\0';
+          }
+        } else {
+          current_is.getline(line, kLineSize);
+        }
       } while ((is_file && RE2::FullMatch(line, *empty_re_)) &&
                !current_is.bad() && !current_is.eof());
 
@@ -276,6 +293,7 @@
 
     if (line[0] != '\0') {
       previous_line = line;
+      linenoiseHistoryAdd(line);
     }
     line_view = absl::string_view(previous_line);
 
@@ -510,14 +528,16 @@
 
     if (std::string str_value, format;
         RE2::FullMatch(line_view, *read_mem_re_, &str_value, &format)) {
-      os << ReadMemory(current_core_, str_value, format) << std::endl;
+      os << kValueColor << ReadMemory(current_core_, str_value, format)
+         << kDefaultColor << std::endl;
       continue;
     }
 
     if (std::string str_value1, format, str_value2; RE2::FullMatch(
             line_view, *write_mem_re_, &str_value1, &format, &str_value2)) {
-      os << WriteMemory(current_core_, str_value1, format, str_value2)
-         << std::endl;
+      os << kValueColor
+         << WriteMemory(current_core_, str_value1, format, str_value2)
+         << kDefaultColor << std::endl;
       continue;
     }
 
@@ -676,7 +696,9 @@
       auto result =
           core_access_[current_core_].debug_interface->ReadRegister(name);
       if (result.ok()) {
-        os << absl::StrCat(name, " = ", absl::Hex(result.value())) << std::endl;
+        os << absl::StrCat(kValueColor, name, " = ", absl::Hex(result.value()),
+                           kDefaultColor)
+           << std::endl;
       } else {
         os << "Error: " << result.status().message() << std::endl;
       }
@@ -976,7 +998,8 @@
 
     if (std::string str_value, format;
         RE2::FullMatch(line_view, *read_mem2_re_, &str_value, &format)) {
-      os << ReadMemory(current_core_, str_value, format) << std::endl;
+      os << kValueColor << ReadMemory(current_core_, str_value, format)
+         << kDefaultColor << std::endl;
       continue;
     }
     // Action points.
@@ -1472,7 +1495,8 @@
   auto result =
       core_access_[current_core_].debug_interface->ReadRegister(reg_name);
   if (result.ok()) {
-    absl::StrAppend(&output, reg_name, " = ", absl::Hex(result.value()));
+    absl::StrAppend(&output, kValueColor, reg_name, " = ",
+                    absl::Hex(result.value()), kDefaultColor);
   } else {
     absl::StrAppend(&output, "Error reading '", reg_name,
                     "': ", result.status().message());
diff --git a/riscv/debug_command_shell.h b/riscv/debug_command_shell.h
index bc8e786..a44bd1c 100644
--- a/riscv/debug_command_shell.h
+++ b/riscv/debug_command_shell.h
@@ -177,6 +177,11 @@
   std::deque<std::string> previous_commands_;
   std::vector<absl::btree_map<int, ActionPointInfo>> core_action_point_info_;
   std::vector<int> core_action_point_id_;
+
+  static constexpr absl::string_view kTextColor = "\033[38;5;58m";
+  static constexpr absl::string_view kSymbolColor = "\033[38;5;21m";
+  static constexpr absl::string_view kValueColor = "\033[38;5;27m";
+  static constexpr absl::string_view kDefaultColor = "\033[0m";
 };
 
 }  // namespace riscv