Introduce line editor library.
authorPeter Collingbourne <peter@pcc.me.uk>
Fri, 31 Jan 2014 23:46:14 +0000 (23:46 +0000)
committerPeter Collingbourne <peter@pcc.me.uk>
Fri, 31 Jan 2014 23:46:14 +0000 (23:46 +0000)
This library will be used by clang-query. I can imagine LLDB becoming another
client of this library, so I think LLVM is a sensible place for it to live.
It wraps libedit, and adds tab completion support.

The code is loosely based on the line editor bits in LLDB, with a few
improvements:

 - Polymorphism for retrieving the list of tab completions, based on
   the concept pattern from the new pass manager.

 - Tab completion doesn't corrupt terminal output if the input covers
   multiple lines. Unfortunately this can only be done in a truly horrible
   way, as far as I can tell. But since the alternative is to implement our
   own line editor (which I don't think LLVM should be in the business of
   doing, at least for now) I think it may be acceptable.

 - Includes a fallback for the case where the user doesn't have libedit
   installed.

Note that this uses C stdio, mainly because libedit also uses C stdio.

Differential Revision: http://llvm-reviews.chandlerc.com/D2200

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@200595 91177308-0d34-0410-b5e6-96231b3b80d8

18 files changed:
autoconf/configure.ac
cmake/config-ix.cmake
configure
include/llvm/Config/config.h.cmake
include/llvm/Config/config.h.in
include/llvm/LineEditor/LineEditor.h [new file with mode: 0644]
lib/CMakeLists.txt
lib/LLVMBuild.txt
lib/LineEditor/CMakeLists.txt [new file with mode: 0644]
lib/LineEditor/LLVMBuild.txt [new file with mode: 0644]
lib/LineEditor/LineEditor.cpp [new file with mode: 0644]
lib/LineEditor/Makefile [new file with mode: 0644]
lib/Makefile
unittests/CMakeLists.txt
unittests/LineEditor/CMakeLists.txt [new file with mode: 0644]
unittests/LineEditor/LineEditor.cpp [new file with mode: 0644]
unittests/LineEditor/Makefile [new file with mode: 0644]
unittests/Makefile

index 7e710080ea070eb0a1dc360c42363d86ee4a62f6..0368f8058525d1112a2ec276fb4f85e079e1b4f7 100644 (file)
@@ -1190,6 +1190,17 @@ AC_ARG_ENABLE(terminfo,AS_HELP_STRING(
   esac],
   llvm_cv_enable_terminfo="yes")
 
   esac],
   llvm_cv_enable_terminfo="yes")
 
+dnl --enable-libedit: check whether the user wants to turn off libedit.
+AC_ARG_ENABLE(libedit,AS_HELP_STRING(
+  [--enable-libedit],
+  [Use libedit if available (default is YES)]),
+  [case "$enableval" in
+    yes) llvm_cv_enable_libedit="yes" ;;
+    no)  llvm_cv_enable_libedit="no"  ;;
+    *) AC_MSG_ERROR([Invalid setting for --enable-libedit. Use "yes" or "no"]) ;;
+  esac],
+  llvm_cv_enable_libedit="yes")
+
 dnl --enable-libffi : check whether the user wants to turn off libffi:
 AC_ARG_ENABLE(libffi,AS_HELP_STRING(
   --enable-libffi,[Check for the presence of libffi (default is NO)]),
 dnl --enable-libffi : check whether the user wants to turn off libffi:
 AC_ARG_ENABLE(libffi,AS_HELP_STRING(
   --enable-libffi,[Check for the presence of libffi (default is NO)]),
@@ -1506,6 +1517,13 @@ if test "$llvm_cv_enable_terminfo" = "yes" ; then
                            [Define if the setupterm() function is supported this platform.]))
 fi
 
                            [Define if the setupterm() function is supported this platform.]))
 fi
 
+dnl The libedit library is optional; used by lib/LineEditor
+if test "$llvm_cv_enable_libedit" = "yes" ; then
+  AC_SEARCH_LIBS(el_init,edit,
+                 AC_DEFINE([HAVE_LIBEDIT],[1],
+                           [Define if libedit is available on this platform.]))
+fi
+
 dnl libffi is optional; used to call external functions from the interpreter
 if test "$llvm_cv_enable_libffi" = "yes" ; then
   AC_SEARCH_LIBS(ffi_call,ffi,AC_DEFINE([HAVE_FFI_CALL],[1],
 dnl libffi is optional; used to call external functions from the interpreter
 if test "$llvm_cv_enable_libffi" = "yes" ; then
   AC_SEARCH_LIBS(ffi_call,ffi,AC_DEFINE([HAVE_FFI_CALL],[1],
index dc991a23be075ad3554717c0218bbb594d158775..c8a857108983451183b7c115547c9e1afd2b9554 100755 (executable)
@@ -97,6 +97,7 @@ if( NOT PURE_WINDOWS )
   else()
     set(HAVE_LIBZ 0)
   endif()
   else()
     set(HAVE_LIBZ 0)
   endif()
+  check_library_exists(edit el_init "" HAVE_LIBEDIT)
   if(LLVM_ENABLE_TERMINFO)
     set(HAVE_TERMINFO 0)
     foreach(library tinfo terminfo curses ncurses ncursesw)
   if(LLVM_ENABLE_TERMINFO)
     set(HAVE_TERMINFO 0)
     foreach(library tinfo terminfo curses ncurses ncursesw)
index 066a34cf7628567d2bccf1c7bb6cdb77dac7c0f3..76440ed1499cd2cf457533e17307df36e1fd9050 100755 (executable)
--- a/configure
+++ b/configure
@@ -1451,6 +1451,7 @@ Optional Features:
                           all,auto,none,{binding-name} (default=auto)
   --enable-terminfo       Query the terminfo database if available (default is
                           YES)
                           all,auto,none,{binding-name} (default=auto)
   --enable-terminfo       Query the terminfo database if available (default is
                           YES)
+  --enable-libedit        Use libedit if available (default is YES)
   --enable-libffi         Check for the presence of libffi (default is NO)
   --enable-ltdl-install   install libltdl
 
   --enable-libffi         Check for the presence of libffi (default is NO)
   --enable-ltdl-install   install libltdl
 
@@ -5681,6 +5682,20 @@ else
 fi
 
 
 fi
 
 
+# Check whether --enable-libedit was given.
+if test "${enable_libedit+set}" = set; then
+  enableval=$enable_libedit; case "$enableval" in
+    yes) llvm_cv_enable_libedit="yes" ;;
+    no)  llvm_cv_enable_libedit="no"  ;;
+    *) { { echo "$as_me:$LINENO: error: Invalid setting for --enable-libedit. Use \"yes\" or \"no\"" >&5
+echo "$as_me: error: Invalid setting for --enable-libedit. Use \"yes\" or \"no\"" >&2;}
+   { (exit 1); exit 1; }; } ;;
+  esac
+else
+  llvm_cv_enable_libedit="yes"
+fi
+
+
 # Check whether --enable-libffi was given.
 if test "${enable_libffi+set}" = set; then
   enableval=$enable_libffi; case "$enableval" in
 # Check whether --enable-libffi was given.
 if test "${enable_libffi+set}" = set; then
   enableval=$enable_libffi; case "$enableval" in
@@ -10663,7 +10678,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<EOF
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<EOF
-#line 10666 "configure"
+#line 10681 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
 
 fi
 
 
 fi
 
+if test "$llvm_cv_enable_libedit" = "yes" ; then
+  { echo "$as_me:$LINENO: checking for library containing el_init" >&5
+echo $ECHO_N "checking for library containing el_init... $ECHO_C" >&6; }
+if test "${ac_cv_search_el_init+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_func_search_save_LIBS=$LIBS
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char el_init ();
+int
+main ()
+{
+return el_init ();
+  ;
+  return 0;
+}
+_ACEOF
+for ac_lib in '' edit; do
+  if test -z "$ac_lib"; then
+    ac_res="none required"
+  else
+    ac_res=-l$ac_lib
+    LIBS="-l$ac_lib  $ac_func_search_save_LIBS"
+  fi
+  rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+        { ac_try='test -z "$ac_c_werror_flag" || test ! -s conftest.err'
+  { (case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+        { ac_try='test -s conftest$ac_exeext'
+  { (case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_search_el_init=$ac_res
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+
+fi
+
+rm -f core conftest.err conftest.$ac_objext \
+      conftest$ac_exeext
+  if test "${ac_cv_search_el_init+set}" = set; then
+  break
+fi
+done
+if test "${ac_cv_search_el_init+set}" = set; then
+  :
+else
+  ac_cv_search_el_init=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_search_el_init" >&5
+echo "${ECHO_T}$ac_cv_search_el_init" >&6; }
+ac_res=$ac_cv_search_el_init
+if test "$ac_res" != no; then
+  test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+cat >>confdefs.h <<\_ACEOF
+#define HAVE_LIBEDIT 1
+_ACEOF
+
+fi
+
+fi
+
 if test "$llvm_cv_enable_libffi" = "yes" ; then
   { echo "$as_me:$LINENO: checking for library containing ffi_call" >&5
 echo $ECHO_N "checking for library containing ffi_call... $ECHO_C" >&6; }
 if test "$llvm_cv_enable_libffi" = "yes" ; then
   { echo "$as_me:$LINENO: checking for library containing ffi_call" >&5
 echo $ECHO_N "checking for library containing ffi_call... $ECHO_C" >&6; }
index 5c72ad8a99c21a2bebdd24c4a780d6f6f23ff8b8..4a15197895ba5025f0024d55ad621e0d21ee4c87 100644 (file)
 /* Define to 1 if you have the 'z' library (-lz). */
 #cmakedefine HAVE_LIBZ ${HAVE_LIBZ}
 
 /* Define to 1 if you have the 'z' library (-lz). */
 #cmakedefine HAVE_LIBZ ${HAVE_LIBZ}
 
+/* Define to 1 if you have the 'edit' library (-ledit). */
+#cmakedefine HAVE_LIBEDIT ${HAVE_LIBEDIT}
+
 /* Define to 1 if you have the <limits.h> header file. */
 #cmakedefine HAVE_LIMITS_H ${HAVE_LIMITS_H}
 
 /* Define to 1 if you have the <limits.h> header file. */
 #cmakedefine HAVE_LIMITS_H ${HAVE_LIMITS_H}
 
index 0d43ae50a14e6ae4deee28501f772fd02fc0f39b..da5b6c9f648a6477de361d2afc235bbf56d447f4 100644 (file)
 /* Define if you have the libdl library or equivalent. */
 #undef HAVE_LIBDL
 
 /* Define if you have the libdl library or equivalent. */
 #undef HAVE_LIBDL
 
+/* Define if libedit is available on this platform. */
+#undef HAVE_LIBEDIT
+
 /* Define to 1 if you have the `imagehlp' library (-limagehlp). */
 #undef HAVE_LIBIMAGEHLP
 
 /* Define to 1 if you have the `imagehlp' library (-limagehlp). */
 #undef HAVE_LIBIMAGEHLP
 
diff --git a/include/llvm/LineEditor/LineEditor.h b/include/llvm/LineEditor/LineEditor.h
new file mode 100644 (file)
index 0000000..7ac9b57
--- /dev/null
@@ -0,0 +1,152 @@
+//===-- llvm/LineEditor/LineEditor.h - line editor --------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LINEEDITOR_LINEEDITOR_H
+#define LLVM_LINEEDITOR_LINEEDITOR_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/OwningPtr.h"
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+namespace llvm {
+
+class LineEditor {
+public:
+  /// Create a LineEditor object.
+  ///
+  /// \param ProgName The name of the current program. Used to form a default
+  /// prompt.
+  /// \param HistoryPath Path to the file in which to store history data, if
+  /// possible.
+  /// \param In The input stream used by the editor.
+  /// \param Out The output stream used by the editor.
+  /// \param Err The error stream used by the editor.
+  LineEditor(StringRef ProgName, StringRef HistoryPath = "", FILE *In = stdin,
+             FILE *Out = stdout, FILE *Err = stderr);
+  ~LineEditor();
+
+  /// Reads a line.
+  ///
+  /// \return The line, or llvm::Optional<std::string>() on EOF.
+  llvm::Optional<std::string> readLine() const;
+
+  void saveHistory();
+  void loadHistory();
+
+  static std::string getDefaultHistoryPath(StringRef ProgName);
+
+  /// The action to perform upon a completion request.
+  struct CompletionAction {
+    enum ActionKind {
+      /// Insert Text at the cursor position.
+      AK_Insert,
+      /// Show Completions, or beep if the list is empty.
+      AK_ShowCompletions
+    };
+
+    ActionKind Kind;
+
+    /// The text to insert.
+    std::string Text;
+
+    /// The list of completions to show.
+    std::vector<std::string> Completions;
+  };
+
+  /// A possible completion at a given cursor position.
+  struct Completion {
+    Completion() {}
+    Completion(const std::string &TypedText, const std::string &DisplayText)
+        : TypedText(TypedText), DisplayText(DisplayText) {}
+
+    /// The text to insert. If the user has already input some of the
+    /// completion, this should only include the rest of the text.
+    std::string TypedText;
+
+    /// A description of this completion. This may be the completion itself, or
+    /// maybe a summary of its type or arguments.
+    std::string DisplayText;
+  };
+
+  /// Set the completer for this LineEditor. A completer is a function object
+  /// which takes arguments of type StringRef (the string to complete) and
+  /// size_t (the zero-based cursor position in the StringRef) and returns a
+  /// CompletionAction.
+  template <typename T> void setCompleter(T Comp) {
+    Completer.reset(new CompleterModel<T>(Comp));
+  }
+
+  /// Set the completer for this LineEditor to the given list completer.
+  /// A list completer is a function object which takes arguments of type
+  /// StringRef (the string to complete) and size_t (the zero-based cursor
+  /// position in the StringRef) and returns a std::vector<Completion>.
+  template <typename T> void setListCompleter(T Comp) {
+    Completer.reset(new ListCompleterModel<T>(Comp));
+  }
+
+  /// Use the current completer to produce a CompletionAction for the given
+  /// completion request. If the current completer is a list completer, this
+  /// will return an AK_Insert CompletionAction if each completion has a common
+  /// prefix, or an AK_ShowCompletions CompletionAction otherwise.
+  ///
+  /// \param Buffer The string to complete
+  /// \param Pos The zero-based cursor position in the StringRef
+  CompletionAction getCompletionAction(StringRef Buffer, size_t Pos) const;
+
+  const std::string &getPrompt() const { return Prompt; }
+  void setPrompt(const std::string &P) { Prompt = P; }
+
+  // Public so callbacks in LineEditor.cpp can use it.
+  struct InternalData;
+
+private:
+  std::string Prompt;
+  std::string HistoryPath;
+  OwningPtr<InternalData> Data;
+
+  struct CompleterConcept {
+    virtual ~CompleterConcept();
+    virtual CompletionAction complete(StringRef Buffer, size_t Pos) const = 0;
+  };
+
+  struct ListCompleterConcept : CompleterConcept {
+    ~ListCompleterConcept();
+    CompletionAction complete(StringRef Buffer, size_t Pos) const;
+    static std::string getCommonPrefix(const std::vector<Completion> &Comps);
+    virtual std::vector<Completion> getCompletions(StringRef Buffer,
+                                                   size_t Pos) const = 0;
+  };
+
+  template <typename T>
+  struct CompleterModel : CompleterConcept {
+    CompleterModel(T Value) : Value(Value) {}
+    CompletionAction complete(StringRef Buffer, size_t Pos) const {
+      return Value(Buffer, Pos);
+    }
+    T Value;
+  };
+
+  template <typename T>
+  struct ListCompleterModel : ListCompleterConcept {
+    ListCompleterModel(T Value) : Value(Value) {}
+    std::vector<Completion> getCompletions(StringRef Buffer, size_t Pos) const {
+      return Value(Buffer, Pos);
+    }
+    T Value;
+  };
+
+  llvm::OwningPtr<const CompleterConcept> Completer;
+};
+
+}
+
+#endif
index 7fbf12339d76d60e97912fd400b849d435cf3b67..9367f553134208550f1a80153decdf4b83bf9d55 100644 (file)
@@ -15,3 +15,4 @@ add_subdirectory(DebugInfo)
 add_subdirectory(ExecutionEngine)
 add_subdirectory(Target)
 add_subdirectory(AsmParser)
 add_subdirectory(ExecutionEngine)
 add_subdirectory(Target)
 add_subdirectory(AsmParser)
+add_subdirectory(LineEditor)
index 00280c8603278b115836914bddc5f048bf0fa960..a0984d410c699d7fb3092746d4d233c557cfbd90 100644 (file)
@@ -16,7 +16,7 @@
 ;===------------------------------------------------------------------------===;
 
 [common]
 ;===------------------------------------------------------------------------===;
 
 [common]
-subdirectories = Analysis AsmParser Bitcode CodeGen DebugInfo ExecutionEngine Linker IR IRReader LTO MC Object Option Support TableGen Target Transforms
+subdirectories = Analysis AsmParser Bitcode CodeGen DebugInfo ExecutionEngine LineEditor Linker IR IRReader LTO MC Object Option Support TableGen Target Transforms
 
 [component_0]
 type = Group
 
 [component_0]
 type = Group
diff --git a/lib/LineEditor/CMakeLists.txt b/lib/LineEditor/CMakeLists.txt
new file mode 100644 (file)
index 0000000..cef36a4
--- /dev/null
@@ -0,0 +1,7 @@
+add_llvm_library(LLVMLineEditor
+  LineEditor.cpp
+  )
+
+if(HAVE_LIBEDIT)
+  target_link_libraries(LLVMLineEditor edit)
+endif()
diff --git a/lib/LineEditor/LLVMBuild.txt b/lib/LineEditor/LLVMBuild.txt
new file mode 100644 (file)
index 0000000..35f4361
--- /dev/null
@@ -0,0 +1,22 @@
+;===- ./lib/LineEditor/LLVMBuild.txt ---------------------------*- Conf -*--===;
+;
+;                     The LLVM Compiler Infrastructure
+;
+; This file is distributed under the University of Illinois Open Source
+; License. See LICENSE.TXT for details.
+;
+;===------------------------------------------------------------------------===;
+;
+; This is an LLVMBuild description file for the components in this subdirectory.
+;
+; For more information on the LLVMBuild system, please see:
+;
+;   http://llvm.org/docs/LLVMBuild.html
+;
+;===------------------------------------------------------------------------===;
+
+[component_0]
+type = Library
+name = LineEditor
+parent = Libraries
+required_libraries = Support
diff --git a/lib/LineEditor/LineEditor.cpp b/lib/LineEditor/LineEditor.cpp
new file mode 100644 (file)
index 0000000..8b7c12a
--- /dev/null
@@ -0,0 +1,325 @@
+//===-- LineEditor.cpp - line editor --------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/LineEditor/LineEditor.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Config/config.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/raw_ostream.h"
+#include <stdio.h>
+#ifdef HAVE_LIBEDIT
+#include <histedit.h>
+#endif
+
+using namespace llvm;
+
+std::string LineEditor::getDefaultHistoryPath(StringRef ProgName) {
+  SmallString<32> Path;
+  if (sys::path::home_directory(Path)) {
+    sys::path::append(Path, "." + ProgName + "-history");
+    return Path.str();
+  }
+  return std::string();
+}
+
+LineEditor::CompleterConcept::~CompleterConcept() {}
+LineEditor::ListCompleterConcept::~ListCompleterConcept() {}
+
+std::string LineEditor::ListCompleterConcept::getCommonPrefix(
+    const std::vector<Completion> &Comps) {
+  assert(!Comps.empty());
+
+  std::string CommonPrefix = Comps[0].TypedText;
+  for (std::vector<Completion>::const_iterator I = Comps.begin() + 1,
+                                               E = Comps.end();
+       I != E; ++I) {
+    size_t Len = std::min(CommonPrefix.size(), I->TypedText.size());
+    size_t CommonLen = 0;
+    for (; CommonLen != Len; ++CommonLen) {
+      if (CommonPrefix[CommonLen] != I->TypedText[CommonLen])
+        break;
+    }
+    CommonPrefix.resize(CommonLen);
+  }
+  return CommonPrefix;
+}
+
+LineEditor::CompletionAction
+LineEditor::ListCompleterConcept::complete(StringRef Buffer, size_t Pos) const {
+  CompletionAction Action;
+  std::vector<Completion> Comps = getCompletions(Buffer, Pos);
+  if (Comps.empty()) {
+    Action.Kind = CompletionAction::AK_ShowCompletions;
+    return Action;
+  }
+
+  std::string CommonPrefix = getCommonPrefix(Comps);
+
+  // If the common prefix is non-empty we can simply insert it. If there is a
+  // single completion, this will insert the full completion. If there is more
+  // than one, this might be enough information to jog the user's memory but if
+  // not the user can also hit tab again to see the completions because the
+  // common prefix will then be empty.
+  if (CommonPrefix.empty()) {
+    Action.Kind = CompletionAction::AK_ShowCompletions;
+    for (std::vector<Completion>::iterator I = Comps.begin(), E = Comps.end();
+         I != E; ++I)
+      Action.Completions.push_back(I->DisplayText);
+  } else {
+    Action.Kind = CompletionAction::AK_Insert;
+    Action.Text = CommonPrefix;
+  }
+
+  return Action;
+}
+
+LineEditor::CompletionAction LineEditor::getCompletionAction(StringRef Buffer,
+                                                             size_t Pos) const {
+  if (!Completer) {
+    CompletionAction Action;
+    Action.Kind = CompletionAction::AK_ShowCompletions;
+    return Action;
+  }
+
+  return Completer->complete(Buffer, Pos);
+}
+
+#ifdef HAVE_LIBEDIT
+
+// libedit-based implementation.
+
+struct LineEditor::InternalData {
+  LineEditor *LE;
+
+  History *Hist;
+  EditLine *EL;
+
+  unsigned PrevCount;
+  std::string ContinuationOutput;
+};
+
+static const char *ElGetPromptFn(EditLine *EL) {
+  LineEditor::InternalData *Data;
+  if (el_get(EL, EL_CLIENTDATA, &Data) == 0)
+    return Data->LE->getPrompt().c_str();
+  return "> ";
+}
+
+// Handles tab completion.
+//
+// This function is really horrible. But since the alternative is to get into
+// the line editor business, here we are.
+static unsigned char ElCompletionFn(EditLine *EL, int ch) {
+  LineEditor::InternalData *Data;
+  if (el_get(EL, EL_CLIENTDATA, &Data) == 0) {
+    if (!Data->ContinuationOutput.empty()) {
+      // This is the continuation of the AK_ShowCompletions branch below.
+      FILE *Out;
+      if (::el_get(EL, EL_GETFP, 1, &Out) != 0)
+        return CC_ERROR;
+
+      // Print the required output (see below).
+      ::fwrite(Data->ContinuationOutput.c_str(),
+               Data->ContinuationOutput.size(), 1, Out);
+
+      // Push a sequence of Ctrl-B characters to move the cursor back to its
+      // original position.
+      std::string Prevs(Data->PrevCount, '\02');
+      ::el_push(EL, (char *)Prevs.c_str());
+
+      Data->ContinuationOutput.clear();
+
+      return CC_REFRESH;
+    }
+
+    const LineInfo *LI = ::el_line(EL);
+    LineEditor::CompletionAction Action = Data->LE->getCompletionAction(
+        StringRef(LI->buffer, LI->lastchar - LI->buffer),
+        LI->cursor - LI->buffer);
+    switch (Action.Kind) {
+    case LineEditor::CompletionAction::AK_Insert:
+      ::el_insertstr(EL, Action.Text.c_str());
+      return CC_REFRESH;
+
+    case LineEditor::CompletionAction::AK_ShowCompletions:
+      if (Action.Completions.empty()) {
+        return CC_REFRESH_BEEP;
+      } else {
+        // Push a Ctrl-E and a tab. The Ctrl-E causes libedit to move the cursor
+        // to the end of the line, so that when we emit a newline we will be on
+        // a new blank line. The tab causes libedit to call this function again
+        // after moving the cursor. There doesn't seem to be anything we can do
+        // from here to cause libedit to move the cursor immediately. This will
+        // break horribly if the user has rebound their keys, so for now we do
+        // not permit user rebinding.
+        ::el_push(EL, (char *)"\05\t");
+
+        // This assembles the output for the continuation block above.
+        raw_string_ostream OS(Data->ContinuationOutput);
+
+        // Move cursor to a blank line.
+        OS << "\n";
+
+        // Emit the completions.
+        for (std::vector<std::string>::iterator I = Action.Completions.begin(),
+                                                E = Action.Completions.end();
+             I != E; ++I) {
+          OS << *I << "\n";
+        }
+
+        // Fool libedit into thinking nothing has changed. Reprint its prompt
+        // and the user input. Note that the cursor will remain at the end of
+        // the line after this.
+        OS << Data->LE->getPrompt()
+           << StringRef(LI->buffer, LI->lastchar - LI->buffer);
+
+        // This is the number of characters we need to tell libedit to go back:
+        // the distance between end of line and the original cursor position.
+        Data->PrevCount = LI->lastchar - LI->cursor;
+
+        return CC_REFRESH;
+      }
+    }
+  } else {
+    return CC_ERROR;
+  }
+}
+
+LineEditor::LineEditor(StringRef ProgName, StringRef HistoryPath, FILE *In,
+                       FILE *Out, FILE *Err)
+    : Prompt((ProgName + "> ").str()), HistoryPath(HistoryPath),
+      Data(new InternalData) {
+  if (HistoryPath.empty())
+    this->HistoryPath = getDefaultHistoryPath(ProgName);
+
+  Data->LE = this;
+
+  Data->Hist = ::history_init();
+  assert(Data->Hist);
+
+  Data->EL = ::el_init(ProgName.str().c_str(), In, Out, Err);
+  assert(Data->EL);
+
+  ::el_set(Data->EL, EL_PROMPT, ElGetPromptFn);
+  ::el_set(Data->EL, EL_EDITOR, "emacs");
+  ::el_set(Data->EL, EL_HIST, history, Data->Hist);
+  ::el_set(Data->EL, EL_ADDFN, "tab_complete", "Tab completion function",
+           ElCompletionFn);
+  ::el_set(Data->EL, EL_BIND, "\t", "tab_complete", NULL);
+  ::el_set(Data->EL, EL_BIND, "^r", "em-inc-search-prev",
+           NULL); // Cycle through backwards search, entering string
+  ::el_set(Data->EL, EL_BIND, "^w", "ed-delete-prev-word",
+           NULL); // Delete previous word, behave like bash does.
+  ::el_set(Data->EL, EL_BIND, "\033[3~", "ed-delete-next-char",
+           NULL); // Fix the delete key.
+  ::el_set(Data->EL, EL_CLIENTDATA, Data.get());
+
+  HistEvent HE;
+  ::history(Data->Hist, &HE, H_SETSIZE, 800);
+  ::history(Data->Hist, &HE, H_SETUNIQUE, 1);
+  loadHistory();
+}
+
+LineEditor::~LineEditor() {
+  saveHistory();
+
+  FILE *Out;
+  if (::el_get(Data->EL, EL_GETFP, 1, &Out) != 0)
+    Out = 0;
+
+  ::history_end(Data->Hist);
+  ::el_end(Data->EL);
+
+  if (Out)
+    ::fwrite("\n", 1, 1, Out);
+}
+
+void LineEditor::saveHistory() {
+  if (!HistoryPath.empty()) {
+    HistEvent HE;
+    ::history(Data->Hist, &HE, H_SAVE, HistoryPath.c_str());
+  }
+}
+
+void LineEditor::loadHistory() {
+  if (!HistoryPath.empty()) {
+    HistEvent HE;
+    ::history(Data->Hist, &HE, H_LOAD, HistoryPath.c_str());
+  }
+}
+
+Optional<std::string> LineEditor::readLine() const {
+  // Call el_gets to prompt the user and read the user's input.
+  int LineLen = 0;
+  const char *Line = ::el_gets(Data->EL, &LineLen);
+
+  // Either of these may mean end-of-file.
+  if (!Line || LineLen == 0)
+    return Optional<std::string>();
+
+  // Strip any newlines off the end of the string.
+  while (LineLen > 0 &&
+         (Line[LineLen - 1] == '\n' || Line[LineLen - 1] == '\r'))
+    --LineLen;
+
+  HistEvent HE;
+  if (LineLen > 0)
+    ::history(Data->Hist, &HE, H_ENTER, Line);
+
+  return std::string(Line, LineLen);
+}
+
+#else
+
+// Simple fgets-based implementation.
+
+struct LineEditor::InternalData {
+  FILE *In;
+  FILE *Out;
+};
+
+LineEditor::LineEditor(StringRef ProgName, StringRef HistoryPath, FILE *In,
+                       FILE *Out, FILE *Err)
+    : Prompt((ProgName + "> ").str()), Data(new InternalData) {
+  Data->In = In;
+  Data->Out = Out;
+}
+
+LineEditor::~LineEditor() {
+  ::fwrite("\n", 1, 1, Data->Out);
+}
+
+void LineEditor::saveHistory() {}
+void LineEditor::loadHistory() {}
+
+Optional<std::string> LineEditor::readLine() const {
+  ::fprintf(Data->Out, "%s", Prompt.c_str());
+
+  std::string Line;
+  do {
+    char Buf[64];
+    char *Res = ::fgets(Buf, sizeof(Buf), Data->In);
+    if (!Res) {
+      if (Line.empty())
+        return Optional<std::string>();
+      else
+        return Line;
+    }
+    Line.append(Buf);
+  } while (Line.empty() ||
+           (Line[Line.size() - 1] != '\n' && Line[Line.size() - 1] != '\r'));
+
+  while (!Line.empty() &&
+         (Line[Line.size() - 1] == '\n' || Line[Line.size() - 1] == '\r'))
+    Line.resize(Line.size() - 1);
+
+  return Line;
+}
+
+#endif
diff --git a/lib/LineEditor/Makefile b/lib/LineEditor/Makefile
new file mode 100644 (file)
index 0000000..c7ff6d8
--- /dev/null
@@ -0,0 +1,15 @@
+##===- lib/LineEditor/Makefile -----------------------------*- Makefile -*-===##
+#
+#                     The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+##===----------------------------------------------------------------------===##
+
+LEVEL = ../..
+LIBRARYNAME = LLVMLineEditor
+BUILD_ARCHIVE := 1
+
+include $(LEVEL)/Makefile.common
+
index 2ed0636cfd2c30980877562e4ce5ffe1c74d2395..a97f71aded08062e1e247e03e06d26e6aff83304 100644 (file)
@@ -12,6 +12,6 @@ include $(LEVEL)/Makefile.config
 
 PARALLEL_DIRS := IR AsmParser Bitcode Analysis Transforms CodeGen Target \
                  ExecutionEngine Linker LTO MC Object Option DebugInfo   \
 
 PARALLEL_DIRS := IR AsmParser Bitcode Analysis Transforms CodeGen Target \
                  ExecutionEngine Linker LTO MC Object Option DebugInfo   \
-                 IRReader
+                 IRReader LineEditor
 
 include $(LEVEL)/Makefile.common
 
 include $(LEVEL)/Makefile.common
index 52702ba23aac616060784a4bc286c0edaddd3bc7..84685e1c24dcb2fd4dac3ccad03088a7e53124dc 100644 (file)
@@ -12,6 +12,7 @@ add_subdirectory(CodeGen)
 add_subdirectory(DebugInfo)
 add_subdirectory(ExecutionEngine)
 add_subdirectory(IR)
 add_subdirectory(DebugInfo)
 add_subdirectory(ExecutionEngine)
 add_subdirectory(IR)
+add_subdirectory(LineEditor)
 add_subdirectory(MC)
 add_subdirectory(Object)
 add_subdirectory(Option)
 add_subdirectory(MC)
 add_subdirectory(Object)
 add_subdirectory(Option)
diff --git a/unittests/LineEditor/CMakeLists.txt b/unittests/LineEditor/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c6823d8
--- /dev/null
@@ -0,0 +1,7 @@
+set(LLVM_LINK_COMPONENTS
+  LineEditor
+  )
+
+add_llvm_unittest(LineEditorTests
+  LineEditor.cpp
+  )
diff --git a/unittests/LineEditor/LineEditor.cpp b/unittests/LineEditor/LineEditor.cpp
new file mode 100644 (file)
index 0000000..cb115bd
--- /dev/null
@@ -0,0 +1,82 @@
+//===-- LineEditor.cpp ----------------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/LineEditor/LineEditor.h"
+#include "llvm/Support/Path.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+class LineEditorTest : public testing::Test {
+public:
+  SmallString<64> HistPath;
+  LineEditor *LE;
+
+  LineEditorTest() {
+    init();
+  }
+
+  void init() {
+    sys::fs::createTemporaryFile("temp", "history", HistPath);
+    ASSERT_FALSE(HistPath.empty());
+    LE = new LineEditor("test", HistPath);
+  }
+
+  ~LineEditorTest() {
+    delete LE;
+    sys::fs::remove(HistPath.str());
+  }
+};
+
+TEST_F(LineEditorTest, HistorySaveLoad) {
+  LE->saveHistory();
+  LE->loadHistory();
+}
+
+struct TestListCompleter {
+  std::vector<LineEditor::Completion> Completions;
+
+  TestListCompleter(const std::vector<LineEditor::Completion> &Completions)
+      : Completions(Completions) {}
+
+  std::vector<LineEditor::Completion> operator()(StringRef Buffer,
+                                                 size_t Pos) const {
+    EXPECT_TRUE(Buffer.empty());
+    EXPECT_EQ(0u, Pos);
+    return Completions;
+  }
+};
+
+TEST_F(LineEditorTest, ListCompleters) {
+  std::vector<LineEditor::Completion> Comps;
+
+  Comps.push_back(LineEditor::Completion("foo", "int foo()"));
+  LE->setListCompleter(TestListCompleter(Comps));
+  LineEditor::CompletionAction CA = LE->getCompletionAction("", 0);
+  EXPECT_EQ(LineEditor::CompletionAction::AK_Insert, CA.Kind);
+  EXPECT_EQ("foo", CA.Text);
+
+  Comps.push_back(LineEditor::Completion("bar", "int bar()"));
+  LE->setListCompleter(TestListCompleter(Comps));
+  CA = LE->getCompletionAction("", 0);
+  EXPECT_EQ(LineEditor::CompletionAction::AK_ShowCompletions, CA.Kind);
+  ASSERT_EQ(2u, CA.Completions.size());
+  ASSERT_EQ("int foo()", CA.Completions[0]);
+  ASSERT_EQ("int bar()", CA.Completions[1]);
+
+  Comps.clear();
+  Comps.push_back(LineEditor::Completion("fee", "int fee()"));
+  Comps.push_back(LineEditor::Completion("fi", "int fi()"));
+  Comps.push_back(LineEditor::Completion("foe", "int foe()"));
+  Comps.push_back(LineEditor::Completion("fum", "int fum()"));
+  LE->setListCompleter(TestListCompleter(Comps));
+  CA = LE->getCompletionAction("", 0);
+  EXPECT_EQ(LineEditor::CompletionAction::AK_Insert, CA.Kind);
+  EXPECT_EQ("f", CA.Text);
+}
diff --git a/unittests/LineEditor/Makefile b/unittests/LineEditor/Makefile
new file mode 100644 (file)
index 0000000..058b6e4
--- /dev/null
@@ -0,0 +1,15 @@
+##===- unittests/LineEditor/Makefile -----------------------*- Makefile -*-===##
+#
+#                     The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+##===----------------------------------------------------------------------===##
+
+LEVEL = ../..
+TESTNAME = LineEditor
+LINK_COMPONENTS := lineeditor
+
+include $(LEVEL)/Makefile.config
+include $(LLVM_SRC_ROOT)/unittests/Makefile.unittest
index dbef6cf9b3b29387b82205819803edfdfc806e79..37f654065ce92aba7f29c0e47b3caec42e0be66e 100644 (file)
@@ -9,8 +9,8 @@
 
 LEVEL = ..
 
 
 LEVEL = ..
 
-PARALLEL_DIRS = ADT Analysis Bitcode CodeGen DebugInfo \
-               ExecutionEngine IR Linker MC Object Option Support Transforms
+PARALLEL_DIRS = ADT Analysis Bitcode CodeGen DebugInfo ExecutionEngine IR \
+               LineEditor Linker MC Object Option Support Transforms
 
 include $(LEVEL)/Makefile.config
 include $(LLVM_SRC_ROOT)/unittests/Makefile.unittest
 
 include $(LEVEL)/Makefile.config
 include $(LLVM_SRC_ROOT)/unittests/Makefile.unittest