[llvm] r200595 - Introduce line editor library.
Peter Collingbourne
peter at pcc.me.uk
Fri Jan 31 15:46:15 PST 2014
Author: pcc
Date: Fri Jan 31 17:46:14 2014
New Revision: 200595
URL: http://llvm.org/viewvc/llvm-project?rev=200595&view=rev
Log:
Introduce line editor library.
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
Added:
llvm/trunk/include/llvm/LineEditor/
llvm/trunk/include/llvm/LineEditor/LineEditor.h
llvm/trunk/lib/LineEditor/
llvm/trunk/lib/LineEditor/CMakeLists.txt
llvm/trunk/lib/LineEditor/LLVMBuild.txt
- copied, changed from r200594, llvm/trunk/lib/LLVMBuild.txt
llvm/trunk/lib/LineEditor/LineEditor.cpp
llvm/trunk/lib/LineEditor/Makefile
- copied, changed from r200594, llvm/trunk/lib/Makefile
llvm/trunk/unittests/LineEditor/
llvm/trunk/unittests/LineEditor/CMakeLists.txt
llvm/trunk/unittests/LineEditor/LineEditor.cpp
llvm/trunk/unittests/LineEditor/Makefile
- copied, changed from r200594, llvm/trunk/unittests/Makefile
Modified:
llvm/trunk/autoconf/configure.ac
llvm/trunk/cmake/config-ix.cmake
llvm/trunk/configure
llvm/trunk/include/llvm/Config/config.h.cmake
llvm/trunk/include/llvm/Config/config.h.in
llvm/trunk/lib/CMakeLists.txt
llvm/trunk/lib/LLVMBuild.txt
llvm/trunk/lib/Makefile
llvm/trunk/unittests/CMakeLists.txt
llvm/trunk/unittests/Makefile
Modified: llvm/trunk/autoconf/configure.ac
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/autoconf/configure.ac?rev=200595&r1=200594&r2=200595&view=diff
==============================================================================
--- llvm/trunk/autoconf/configure.ac (original)
+++ llvm/trunk/autoconf/configure.ac Fri Jan 31 17:46:14 2014
@@ -1190,6 +1190,17 @@ AC_ARG_ENABLE(terminfo,AS_HELP_STRING(
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)]),
@@ -1506,6 +1517,13 @@ if test "$llvm_cv_enable_terminfo" = "ye
[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],
Modified: llvm/trunk/cmake/config-ix.cmake
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/cmake/config-ix.cmake?rev=200595&r1=200594&r2=200595&view=diff
==============================================================================
--- llvm/trunk/cmake/config-ix.cmake (original)
+++ llvm/trunk/cmake/config-ix.cmake Fri Jan 31 17:46:14 2014
@@ -97,6 +97,7 @@ if( NOT PURE_WINDOWS )
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)
Modified: llvm/trunk/configure
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/configure?rev=200595&r1=200594&r2=200595&view=diff
==============================================================================
--- llvm/trunk/configure (original)
+++ llvm/trunk/configure Fri Jan 31 17:46:14 2014
@@ -1451,6 +1451,7 @@ Optional Features:
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
@@ -5681,6 +5682,20 @@ else
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
@@ -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
-#line 10666 "configure"
+#line 10681 "configure"
#include "confdefs.h"
#if HAVE_DLFCN_H
@@ -12555,6 +12570,112 @@ cat >>confdefs.h <<\_ACEOF
_ACEOF
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
Modified: llvm/trunk/include/llvm/Config/config.h.cmake
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/Config/config.h.cmake?rev=200595&r1=200594&r2=200595&view=diff
==============================================================================
--- llvm/trunk/include/llvm/Config/config.h.cmake (original)
+++ llvm/trunk/include/llvm/Config/config.h.cmake Fri Jan 31 17:46:14 2014
@@ -212,6 +212,9 @@
/* 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}
Modified: llvm/trunk/include/llvm/Config/config.h.in
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/Config/config.h.in?rev=200595&r1=200594&r2=200595&view=diff
==============================================================================
--- llvm/trunk/include/llvm/Config/config.h.in (original)
+++ llvm/trunk/include/llvm/Config/config.h.in Fri Jan 31 17:46:14 2014
@@ -205,6 +205,9 @@
/* 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
Added: llvm/trunk/include/llvm/LineEditor/LineEditor.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/LineEditor/LineEditor.h?rev=200595&view=auto
==============================================================================
--- llvm/trunk/include/llvm/LineEditor/LineEditor.h (added)
+++ llvm/trunk/include/llvm/LineEditor/LineEditor.h Fri Jan 31 17:46:14 2014
@@ -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
Modified: llvm/trunk/lib/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/CMakeLists.txt?rev=200595&r1=200594&r2=200595&view=diff
==============================================================================
--- llvm/trunk/lib/CMakeLists.txt (original)
+++ llvm/trunk/lib/CMakeLists.txt Fri Jan 31 17:46:14 2014
@@ -15,3 +15,4 @@ add_subdirectory(DebugInfo)
add_subdirectory(ExecutionEngine)
add_subdirectory(Target)
add_subdirectory(AsmParser)
+add_subdirectory(LineEditor)
Modified: llvm/trunk/lib/LLVMBuild.txt
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/LLVMBuild.txt?rev=200595&r1=200594&r2=200595&view=diff
==============================================================================
--- llvm/trunk/lib/LLVMBuild.txt (original)
+++ llvm/trunk/lib/LLVMBuild.txt Fri Jan 31 17:46:14 2014
@@ -16,7 +16,7 @@
;===------------------------------------------------------------------------===;
[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
Added: llvm/trunk/lib/LineEditor/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/LineEditor/CMakeLists.txt?rev=200595&view=auto
==============================================================================
--- llvm/trunk/lib/LineEditor/CMakeLists.txt (added)
+++ llvm/trunk/lib/LineEditor/CMakeLists.txt Fri Jan 31 17:46:14 2014
@@ -0,0 +1,7 @@
+add_llvm_library(LLVMLineEditor
+ LineEditor.cpp
+ )
+
+if(HAVE_LIBEDIT)
+ target_link_libraries(LLVMLineEditor edit)
+endif()
Copied: llvm/trunk/lib/LineEditor/LLVMBuild.txt (from r200594, llvm/trunk/lib/LLVMBuild.txt)
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/LineEditor/LLVMBuild.txt?p2=llvm/trunk/lib/LineEditor/LLVMBuild.txt&p1=llvm/trunk/lib/LLVMBuild.txt&r1=200594&r2=200595&rev=200595&view=diff
==============================================================================
--- llvm/trunk/lib/LLVMBuild.txt (original)
+++ llvm/trunk/lib/LineEditor/LLVMBuild.txt Fri Jan 31 17:46:14 2014
@@ -1,4 +1,4 @@
-;===- ./lib/LLVMBuild.txt --------------------------------------*- Conf -*--===;
+;===- ./lib/LineEditor/LLVMBuild.txt ---------------------------*- Conf -*--===;
;
; The LLVM Compiler Infrastructure
;
@@ -15,10 +15,8 @@
;
;===------------------------------------------------------------------------===;
-[common]
-subdirectories = Analysis AsmParser Bitcode CodeGen DebugInfo ExecutionEngine Linker IR IRReader LTO MC Object Option Support TableGen Target Transforms
-
[component_0]
-type = Group
-name = Libraries
-parent = $ROOT
+type = Library
+name = LineEditor
+parent = Libraries
+required_libraries = Support
Added: llvm/trunk/lib/LineEditor/LineEditor.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/LineEditor/LineEditor.cpp?rev=200595&view=auto
==============================================================================
--- llvm/trunk/lib/LineEditor/LineEditor.cpp (added)
+++ llvm/trunk/lib/LineEditor/LineEditor.cpp Fri Jan 31 17:46:14 2014
@@ -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
Copied: llvm/trunk/lib/LineEditor/Makefile (from r200594, llvm/trunk/lib/Makefile)
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/LineEditor/Makefile?p2=llvm/trunk/lib/LineEditor/Makefile&p1=llvm/trunk/lib/Makefile&r1=200594&r2=200595&rev=200595&view=diff
==============================================================================
--- llvm/trunk/lib/Makefile (original)
+++ llvm/trunk/lib/LineEditor/Makefile Fri Jan 31 17:46:14 2014
@@ -1,4 +1,4 @@
-##===- lib/Makefile ----------------------------------------*- Makefile -*-===##
+##===- lib/LineEditor/Makefile -----------------------------*- Makefile -*-===##
#
# The LLVM Compiler Infrastructure
#
@@ -6,12 +6,10 @@
# License. See LICENSE.TXT for details.
#
##===----------------------------------------------------------------------===##
-LEVEL = ..
-include $(LEVEL)/Makefile.config
-
-PARALLEL_DIRS := IR AsmParser Bitcode Analysis Transforms CodeGen Target \
- ExecutionEngine Linker LTO MC Object Option DebugInfo \
- IRReader
+LEVEL = ../..
+LIBRARYNAME = LLVMLineEditor
+BUILD_ARCHIVE := 1
include $(LEVEL)/Makefile.common
+
Modified: llvm/trunk/lib/Makefile
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Makefile?rev=200595&r1=200594&r2=200595&view=diff
==============================================================================
--- llvm/trunk/lib/Makefile (original)
+++ llvm/trunk/lib/Makefile Fri Jan 31 17:46:14 2014
@@ -12,6 +12,6 @@ include $(LEVEL)/Makefile.config
PARALLEL_DIRS := IR AsmParser Bitcode Analysis Transforms CodeGen Target \
ExecutionEngine Linker LTO MC Object Option DebugInfo \
- IRReader
+ IRReader LineEditor
include $(LEVEL)/Makefile.common
Modified: llvm/trunk/unittests/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/CMakeLists.txt?rev=200595&r1=200594&r2=200595&view=diff
==============================================================================
--- llvm/trunk/unittests/CMakeLists.txt (original)
+++ llvm/trunk/unittests/CMakeLists.txt Fri Jan 31 17:46:14 2014
@@ -12,6 +12,7 @@ add_subdirectory(CodeGen)
add_subdirectory(DebugInfo)
add_subdirectory(ExecutionEngine)
add_subdirectory(IR)
+add_subdirectory(LineEditor)
add_subdirectory(MC)
add_subdirectory(Object)
add_subdirectory(Option)
Added: llvm/trunk/unittests/LineEditor/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/LineEditor/CMakeLists.txt?rev=200595&view=auto
==============================================================================
--- llvm/trunk/unittests/LineEditor/CMakeLists.txt (added)
+++ llvm/trunk/unittests/LineEditor/CMakeLists.txt Fri Jan 31 17:46:14 2014
@@ -0,0 +1,7 @@
+set(LLVM_LINK_COMPONENTS
+ LineEditor
+ )
+
+add_llvm_unittest(LineEditorTests
+ LineEditor.cpp
+ )
Added: llvm/trunk/unittests/LineEditor/LineEditor.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/LineEditor/LineEditor.cpp?rev=200595&view=auto
==============================================================================
--- llvm/trunk/unittests/LineEditor/LineEditor.cpp (added)
+++ llvm/trunk/unittests/LineEditor/LineEditor.cpp Fri Jan 31 17:46:14 2014
@@ -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);
+}
Copied: llvm/trunk/unittests/LineEditor/Makefile (from r200594, llvm/trunk/unittests/Makefile)
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/LineEditor/Makefile?p2=llvm/trunk/unittests/LineEditor/Makefile&p1=llvm/trunk/unittests/Makefile&r1=200594&r2=200595&rev=200595&view=diff
==============================================================================
--- llvm/trunk/unittests/Makefile (original)
+++ llvm/trunk/unittests/LineEditor/Makefile Fri Jan 31 17:46:14 2014
@@ -1,4 +1,4 @@
-##===- unittests/Makefile ----------------------------------*- Makefile -*-===##
+##===- unittests/LineEditor/Makefile -----------------------*- Makefile -*-===##
#
# The LLVM Compiler Infrastructure
#
@@ -7,13 +7,9 @@
#
##===----------------------------------------------------------------------===##
-LEVEL = ..
-
-PARALLEL_DIRS = ADT Analysis Bitcode CodeGen DebugInfo \
- ExecutionEngine IR Linker MC Object Option Support Transforms
+LEVEL = ../..
+TESTNAME = LineEditor
+LINK_COMPONENTS := lineeditor
include $(LEVEL)/Makefile.config
include $(LLVM_SRC_ROOT)/unittests/Makefile.unittest
-
-clean::
- $(Verb) $(RM) -f *Tests
Modified: llvm/trunk/unittests/Makefile
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/Makefile?rev=200595&r1=200594&r2=200595&view=diff
==============================================================================
--- llvm/trunk/unittests/Makefile (original)
+++ llvm/trunk/unittests/Makefile Fri Jan 31 17:46:14 2014
@@ -9,8 +9,8 @@
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
More information about the llvm-commits
mailing list