Added color and history to cheriot debug cli

PiperOrigin-RevId: 903386333
Change-Id: I1a059cfbde451479c43f14206adf4fc8abbf5e9a
diff --git a/MODULE.bazel b/MODULE.bazel
index 4655aa1..dfbae2f 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -57,6 +57,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/cheriot/BUILD b/cheriot/BUILD
index b05d113..e430887 100644
--- a/cheriot/BUILD
+++ b/cheriot/BUILD
@@ -556,6 +556,7 @@
         "@abseil-cpp//absl/strings",
         "@abseil-cpp//absl/strings:str_format",
         "@com_googlesource_code_re2//:re2",
+        "@linenoise",
         "@mpact-riscv//riscv:stoull_wrapper",
         "@mpact-sim//mpact/sim/generic:core",
         "@mpact-sim//mpact/sim/generic:core_debug_interface",
diff --git a/cheriot/cheriot_state.cc b/cheriot/cheriot_state.cc
index d1dff0f..801c793 100644
--- a/cheriot/cheriot_state.cc
+++ b/cheriot/cheriot_state.cc
@@ -195,7 +195,7 @@
 
   // minstret/minstreth
   auto* minstret = CreateCsr<RiscVCounterCsr<T, CheriotState>>(
-      state, csr_vec, "minstret", RiscVCsrEnum ::kMInstret, state);
+      state, csr_vec, "minstret", RiscVCsrEnum::kMInstret, state);
   CHECK_NE(minstret, nullptr);
   auto minstreth = CreateCsr<RiscVCounterCsrHigh<CheriotState>>(
       state, csr_vec, "minstreth", RiscVCsrEnum::kMInstretH, state,
@@ -237,7 +237,7 @@
   // User level CSRs
   // instret/instreth
   CHECK_NE(CreateCsr<RiscVShadowCsr<T>>(
-               state, csr_vec, "instret", RiscVCsrEnum ::kInstret,
+               state, csr_vec, "instret", RiscVCsrEnum::kInstret,
                std::numeric_limits<T>::max(), 0, state, minstret),
            nullptr);
   CHECK_NE(CreateCsr<RiscVShadowCsr<T>>(
diff --git a/cheriot/debug_command_shell.cc b/cheriot/debug_command_shell.cc
index 613834d..fb1db03 100644
--- a/cheriot/debug_command_shell.cc
+++ b/cheriot/debug_command_shell.cc
@@ -15,6 +15,7 @@
 #include "cheriot/debug_command_shell.h"
 
 #include <cstdint>
+#include <cstdlib>
 #include <cstring>
 #include <fstream>
 #include <functional>
@@ -39,6 +40,7 @@
 #include "cheriot/cheriot_top.h"
 #include "cheriot/riscv_cheriot_enums.h"
 #include "cheriot/riscv_cheriot_register_aliases.h"
+#include "linenoise.h"
 #include "mpact/sim/generic/core_debug_interface.h"
 #include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/instruction.h"
@@ -261,11 +263,12 @@
   absl::string_view line_view;
   bool halt_reason = false;
   int last_info_list_size = 0;
+  linenoiseHistorySetMaxLen(1000);
   while (true) {
     // Prompt and read in the next command.
     auto pcc_result =
         core_access_[current_core_].debug_interface->ReadRegister("pcc");
-    std::string prompt;
+    std::string pre_prompt;
     if (halt_reason) {
       halt_reason = false;
       auto result =
@@ -273,49 +276,53 @@
       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 InterruptListener::kInterruptTaken:
-            absl::StrAppend(&prompt, "Stopped at taken interrupt\n");
+            absl::StrAppend(&pre_prompt, "Stopped at taken interrupt\n");
             break;
           case InterruptListener::kInterruptReturn:
-            absl::StrAppend(&prompt,
+            absl::StrAppend(&pre_prompt,
                             "Stopped at return from interrupt handler\n");
             break;
           case InterruptListener::kExceptionTaken:
-            absl::StrAppend(&prompt, "Stopped at exception\n");
+            absl::StrAppend(&pre_prompt, "Stopped at exception\n");
             break;
           case InterruptListener::kExceptionReturn:
-            absl::StrAppend(&prompt, "Stopped at return exception handler\n");
+            absl::StrAppend(&pre_prompt,
+                            "Stopped at return exception handler\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 (pcc_result.ok()) {
       auto* loader = core_access_[current_core_].loader_getter();
       if (loader != nullptr) {
         auto fcn_result = loader->GetFunctionName(pcc_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(pcc_result.value());
         if (symbol_result.ok()) {
-          absl::StrAppend(&prompt, symbol_result.value(), ":\n");
+          absl::StrAppend(&pre_prompt, kSymbolColor, symbol_result.value(),
+                          ":\n", kDefaultColor);
         }
       }
       absl::StrAppend(&prompt,
@@ -324,9 +331,10 @@
           core_access_[current_core_].debug_interface->GetDisassembly(
               pcc_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");
     }
     auto cheriot_state =
         static_cast<CheriotState*>(core_access_[current_core_].state);
@@ -334,12 +342,15 @@
     // Check if there is a new interrupt, if so print the info.
     if (info_list.size() > last_info_list_size) {
       auto const& info = *info_list.rbegin();
-      absl::StrAppend(&prompt, info.is_interrupt ? "interrupt " : "exception ",
+      absl::StrAppend(&pre_prompt,
+                      info.is_interrupt ? "interrupt " : "exception ",
                       info.is_interrupt ? GetInterruptDescription(info)
                                         : GetExceptionDescription(info));
     }
     last_info_list_size = info_list.size();
-    absl::StrAppend(&prompt, "[", current_core_, "] > ");
+
+    if (!pre_prompt.empty()) absl::StrAppend(&pre_prompt, "\n");
+    absl::StrAppend(&prompt, " [", current_core_, "] > ");
     while (!command_streams_.empty()) {
       auto& current_is = *command_streams_.back();
       // Ignore comments or empty lines.
@@ -347,8 +358,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());
 
@@ -378,6 +399,7 @@
 
     if (line[0] != '\0') {
       previous_line = line;
+      linenoiseHistoryAdd(line);
     }
     line_view = absl::string_view(previous_line);
 
@@ -1638,11 +1660,12 @@
                    values[6] & PB::kPermitUnseal ? "U" : "-",
                    values[6] & PB::kUserPerm0 ? "0" : "-");
   absl::StrAppend(
-      &output,
+      &output, kValueColor,
       absl::StrFormat(
           "%-5s = 0x%08x (v: %1x 0x%08x-0x%09x l: 0x%09x o: 0x%x p: %s)",
           reg_name, values[0], values[1], values[2], values[3], values[4],
-          obj_type, permissions));
+          obj_type, permissions),
+      kDefaultColor);
   return output;
 }
 
@@ -1660,7 +1683,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/cheriot/debug_command_shell.h b/cheriot/debug_command_shell.h
index 36d52d1..bf5a108 100644
--- a/cheriot/debug_command_shell.h
+++ b/cheriot/debug_command_shell.h
@@ -251,6 +251,11 @@
   std::vector<absl::btree_map<int, ActionPointInfo>> core_action_point_info_;
   std::vector<int> core_action_point_id_;
   std::vector<InterruptListener*> interrupt_listeners_;
+
+  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 cheriot