From: Ruben Undheim <ruben.undheim@gmail.com>
Date: Sun, 31 Mar 2019 21:34:17 +0200
Subject: Use readline for interactive input. This adds 'command line
 completion', search functionality etc

---
 CMakeLists.txt                   |   5 ++
 app/StaMain.cc                   | 142 ++++++++++++++++++++++++++++++++++++++-
 cmake/modules/FindReadline.cmake |  47 +++++++++++++
 3 files changed, 191 insertions(+), 3 deletions(-)
 create mode 100644 cmake/modules/FindReadline.cmake

diff --git a/CMakeLists.txt b/CMakeLists.txt
index cc5e26e..1e9a7bc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,6 +18,8 @@ cmake_minimum_required (VERSION 3.9)
 
 project(STA VERSION 2.0.17)
 
+set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules CACHE STRING "CMake module path")
+
 set(CMAKE_VERBOSE_MAKEFILE ON)
 set(CMAKE_CXX_STANDARD 11)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -563,6 +565,7 @@ get_filename_component(TCL_HEADER_DIR "${TCL_HEADER}" PATH)
 
 find_package(FLEX)
 find_package(BISON)
+find_package(Readline REQUIRED)
 
 # LibertyExpr scan/parse.
 bison_target(LibertyExprParser liberty/LibertyExprParse.yy ${STA_HOME}/liberty/LibertyExprParse.cc
@@ -707,6 +710,8 @@ if (ZLIB_FOUND)
   target_link_libraries(sta ${ZLIB_LIBRARIES})
 endif()
 
+target_link_libraries(sta ${Readline_LIBRARY})
+
 message(STATUS "STA executable: ${STA_HOME}/app/sta")
 
 # g++ std::thread apparently still needs -pthreads.
diff --git a/app/StaMain.cc b/app/StaMain.cc
index 2d96ae2..f4199c7 100644
--- a/app/StaMain.cc
+++ b/app/StaMain.cc
@@ -16,6 +16,8 @@
 
 #include <tcl.h>
 #include <stdlib.h>
+#include <readline/readline.h>
+#include <readline/history.h>
 #include "Machine.hh"
 #include "StringUtil.hh"
 #include "Vector.hh"
@@ -26,6 +28,12 @@ namespace sta {
 
 typedef sta::Vector<SwigInitFunc> SwigInitFuncSeq;
 
+char **extra_command_completion (const char *, int, int);
+char *extra_command_generator (const char *, int);
+int command_exit (ClientData, Tcl_Interp *, int, Tcl_Obj *const []);
+void save_history();
+void load_history();
+
 // "Arguments" passed to staTclAppInit.
 static int sta_argc;
 static char **sta_argv;
@@ -33,6 +41,8 @@ static const char *sta_init_filename;
 static const char **sta_tcl_inits;
 static SwigInitFunc sta_swig_init;
 
+static bool ended = 0;
+
 void
 staMain(Sta *sta,
 	int argc,
@@ -41,6 +51,10 @@ staMain(Sta *sta,
 	SwigInitFunc swig_init,
 	const char *tcl_inits[])
 {
+  char *buffer;
+  Tcl_Interp *myInterp;
+  int status;
+
   initSta();
 
   Sta::setSta(sta);
@@ -50,9 +64,31 @@ staMain(Sta *sta,
   sta->setThreadCount(thread_count);
 
   staSetupAppInit(argc, argv, init_filename, swig_init, tcl_inits);
-  // Set argc to 1 so Tcl_Main doesn't source any files.
-  // Tcl_Main never returns.
-  Tcl_Main(1, argv, staTclAppInit);
+
+  myInterp = Tcl_CreateInterp();
+  Tcl_CreateObjCommand(myInterp, "exit", (Tcl_ObjCmdProc*)command_exit, 0, 0);
+
+  rl_attempted_completion_function = extra_command_completion;
+
+  staTclAppInit(myInterp);
+
+  load_history();
+
+  while((!ended) && (buffer = readline("OpenSTA> ")) != NULL) {
+    status = Tcl_Eval(myInterp, buffer);
+    if(status != TCL_OK) {
+      fprintf(stderr, "%s\n", Tcl_GetStringResult(myInterp));
+    }
+    if (buffer[0] != 0)
+      add_history(buffer);
+    free(buffer);
+    if(ended) break;
+  }
+
+  save_history();
+
+  Tcl_DeleteInterp(myInterp);
+  Tcl_Finalize();
 }
 
 int
@@ -220,6 +256,106 @@ evalTclInit(Tcl_Interp *interp,
   delete [] unencoded;
 }
 
+int command_exit(ClientData, Tcl_Interp *, int, Tcl_Obj *const [])
+{
+  ended = 1;
+  return 0;
+}
+
+char const *extra_commands[] = {
+    "all_clocks",
+    "all_inputs",
+    "all_outputs",
+    "all_registers",
+    "check_setup",
+    "create_clock",
+    "create_generated_clock",
+    "create_voltage_area",
+    "current_design",
+    "current_instance",
+    "define_corners",
+
+    "get_clocks",
+    "get_fanin",
+    "get_fanout",
+
+    "get_nets",
+    "get_pins",
+    "get_ports",
+    "read_liberty",
+    "read_parasitics",
+    "read_sdc",
+    "read_sdf",
+    "read_spef",
+    "read_verilog",
+    "report_annotated_delay",
+    "report_cell",
+    "report_checks",
+    "report_path",
+    "report_slack",
+    "set_input_delay",
+    "write_sdc",
+    "write_sdf",
+    NULL
+};
+
+char **extra_command_completion(const char *text, int, int)
+{
+  rl_attempted_completion_over = 0;
+  return rl_completion_matches(text, extra_command_generator);
+}
+
+char *extra_command_generator(const char *text, int state)
+{
+  static int list_index, len;
+  const char *name;
+
+  if (!state) {
+    list_index = 0;
+    len = strlen(text);
+  }
+
+  while ((name = extra_commands[list_index++])) {
+    if(strncmp(name, text, len) == 0) {
+      return strdup(name);
+    }
+  }
+
+  return NULL;
+}
+
+void load_history()
+{
+  FILE *histin = fopen(".history_sta", "r");
+  if(histin != NULL) {
+    char *line = NULL;
+    size_t len = 0;
+    ssize_t read;
+    while((read = getline(&line, &len, histin)) != -1) {
+      line[strlen(line)-1] = 0;
+      if (line[0] != 0) {
+        add_history(line);
+      }
+    }
+    fclose(histin);
+  }
+}
+
+void save_history()
+{
+  printf("Saving command history\n");
+  HIST_ENTRY **the_list;
+  the_list = history_list();
+  if(the_list){
+    FILE *histout = fopen(".history_sta", "w");
+    for(int i=0; the_list[i] ; i++) {
+      fprintf(histout, "%s\n", the_list[i]->line);
+    }
+    fclose(histout);
+  }
+}
+
+
 void
 showUsage(const char * prog) 
 {
diff --git a/cmake/modules/FindReadline.cmake b/cmake/modules/FindReadline.cmake
new file mode 100644
index 0000000..4dcb649
--- /dev/null
+++ b/cmake/modules/FindReadline.cmake
@@ -0,0 +1,47 @@
+# - Try to find readline include dirs and libraries 
+#
+# Usage of this module as follows:
+#
+#     find_package(Readline)
+#
+# Variables used by this module, they can change the default behaviour and need
+# to be set before calling find_package:
+#
+#  Readline_ROOT_DIR         Set this variable to the root installation of
+#                            readline if the module has problems finding the
+#                            proper installation path.
+#
+# Variables defined by this module:
+#
+#  READLINE_FOUND            System has readline, include and lib dirs found
+#  Readline_INCLUDE_PATH      The readline include directories. 
+#  Readline_LIBRARY          The readline library.
+
+find_path(Readline_ROOT_DIR
+    NAMES include/readline/readline.h
+)
+
+find_path(Readline_INCLUDE_PATH
+    NAMES readline/readline.h
+    HINTS ${Readline_ROOT_DIR}/include
+)
+
+find_library(Readline_LIBRARY
+    NAMES readline
+    HINTS ${Readline_ROOT_DIR}/lib
+)
+
+if(Readline_INCLUDE_PATH AND Readline_LIBRARY AND Ncurses_LIBRARY)
+  set(READLINE_FOUND TRUE)
+else(Readline_INCLUDE_PATH AND Readline_LIBRARY AND Ncurses_LIBRARY)
+  FIND_LIBRARY(Readline_LIBRARY NAMES readline)
+  include(FindPackageHandleStandardArgs)
+  FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG Readline_INCLUDE_PATH Readline_LIBRARY )
+  MARK_AS_ADVANCED(Readline_INCLUDE_PATH Readline_LIBRARY)
+endif(Readline_INCLUDE_PATH AND Readline_LIBRARY AND Ncurses_LIBRARY)
+
+mark_as_advanced(
+    Readline_ROOT_DIR
+    Readline_INCLUDE_PATH
+    Readline_LIBRARY
+)
