Index: llvm/CMakeLists.txt =================================================================== --- llvm/CMakeLists.txt +++ llvm/CMakeLists.txt @@ -360,6 +360,7 @@ set(LLVM_ENABLE_LIBXML2 "ON" CACHE STRING "Use libxml2 if available. Can be ON, OFF, or FORCE_ON") option(LLVM_ENABLE_LIBEDIT "Use libedit if available." ON) +option(LLVM_ENABLE_LIBREADLINE "Use libreadline if available." ON) option(LLVM_ENABLE_LIBPFM "Use libpfm for performance counters if available." ON) Index: llvm/cmake/config-ix.cmake =================================================================== --- llvm/cmake/config-ix.cmake +++ llvm/cmake/config-ix.cmake @@ -64,6 +64,7 @@ check_include_file(mach/mach.h HAVE_MACH_MACH_H) check_include_file(histedit.h HAVE_HISTEDIT_H) +check_include_file(readline/readline.h HAVE_READLINE_H) check_include_file(CrashReporterClient.h HAVE_CRASHREPORTERCLIENT_H) if(APPLE) include(CheckCSourceCompiles) @@ -169,6 +170,11 @@ else() set(HAVE_LIBEDIT 0) endif() + if (LLVM_ENABLE_LIBREADLINE AND HAVE_READLINE_H) + check_library_exists(readline readline "" HAVE_LIBREADLINE) + else() + set(HAVE_LIBREADLINE 0) + endif() if(LLVM_ENABLE_TERMINFO STREQUAL FORCE_ON) set(MAYBE_REQUIRED REQUIRED) else() Index: llvm/include/llvm/Config/config.h.cmake =================================================================== --- llvm/include/llvm/Config/config.h.cmake +++ llvm/include/llvm/Config/config.h.cmake @@ -103,6 +103,9 @@ /* Define to 1 if you have the `edit' library (-ledit). */ #cmakedefine HAVE_LIBEDIT ${HAVE_LIBEDIT} +/* Define to 1 if you have the `readline' library (-lreadline). */ +#cmakedefine HAVE_LIBREADLINE ${HAVE_LIBREADLINE} + /* Define to 1 if you have the `pfm' library (-lpfm). */ #cmakedefine HAVE_LIBPFM ${HAVE_LIBPFM} Index: llvm/lib/LineEditor/CMakeLists.txt =================================================================== --- llvm/lib/LineEditor/CMakeLists.txt +++ llvm/lib/LineEditor/CMakeLists.txt @@ -1,6 +1,9 @@ if(HAVE_LIBEDIT) set(link_libs edit) endif() +if(HAVE_LIBREADLINE) + set(link_libs readline) +endif() add_llvm_component_library(LLVMLineEditor LineEditor.cpp Index: llvm/lib/LineEditor/LineEditor.cpp =================================================================== --- llvm/lib/LineEditor/LineEditor.cpp +++ llvm/lib/LineEditor/LineEditor.cpp @@ -14,12 +14,42 @@ #include #include #include -#ifdef HAVE_LIBEDIT +#if defined(HAVE_LIBEDIT) #include +#elif defined(HAVE_LIBREADLINE) +#include +#include +#include #endif using namespace llvm; +#if !defined(HAVE_LIBEDIT) +static Optional PromptOnFILE(const std::string &Prompt, FILE *In, + FILE *Out) { + ::fprintf(Out, "%s", Prompt.c_str()); + + std::string Line; + do { + char Buf[64]; + char *Res = ::fgets(Buf, sizeof(Buf), In); + if (!Res) { + if (Line.empty()) + return Optional(); + 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 + std::string LineEditor::getDefaultHistoryPath(StringRef ProgName) { SmallString<32> Path; if (sys::path::home_directory(Path)) { @@ -91,7 +121,7 @@ return Completer->complete(Buffer, Pos); } -#ifdef HAVE_LIBEDIT +#if defined(HAVE_LIBEDIT) // libedit-based implementation. @@ -198,7 +228,7 @@ LineEditor::LineEditor(StringRef ProgName, StringRef HistoryPath, FILE *In, FILE *Out, FILE *Err) : Prompt((ProgName + "> ").str()), HistoryPath(std::string(HistoryPath)), - Data(new InternalData) { + Data(std::make_unique()) { if (HistoryPath.empty()) this->HistoryPath = getDefaultHistoryPath(ProgName); @@ -274,51 +304,84 @@ return std::string(Line, LineLen); } -#else // HAVE_LIBEDIT +#elif defined(HAVE_LIBREADLINE) -// Simple fgets-based implementation. +// libreadline-based implementation. struct LineEditor::InternalData { FILE *In; FILE *Out; + bool UseReadline; }; LineEditor::LineEditor(StringRef ProgName, StringRef HistoryPath, FILE *In, FILE *Out, FILE *Err) - : Prompt((ProgName + "> ").str()), Data(new InternalData) { + : Prompt((ProgName + "> ").str()), Data(std::make_unique()) { Data->In = In; Data->Out = Out; + Data->UseReadline = Out == stdout && isatty(STDOUT_FILENO) && + In == stdin && isatty(STDIN_FILENO); + + if (Data->UseReadline) { + // FIXME: Set up tab completion. For now, tab inserts itself. + rl_bind_key('\t', rl_insert); + } } LineEditor::~LineEditor() { ::fwrite("\n", 1, 1, Data->Out); } -void LineEditor::saveHistory() {} -void LineEditor::loadHistory() {} +void LineEditor::saveHistory() { + // FIXME: Implement. +} +void LineEditor::loadHistory() { + // FIXME: Implement. +} Optional LineEditor::readLine() const { - ::fprintf(Data->Out, "%s", Prompt.c_str()); + // FIXME: Maybe LineEditor should have a Create() factory method that + // returns an FgetsLineEditor() object if !Data->UseReadline, instead of + // storing this state and doing the check here. + if (!Data->UseReadline) + return PromptOnFILE(Prompt, Data->Out, Data->In); std::string Line; - do { - char Buf[64]; - char *Res = ::fgets(Buf, sizeof(Buf), Data->In); - if (!Res) { - if (Line.empty()) - return Optional(); - else - return Line; - } - Line.append(Buf); - } while (Line.empty() || - (Line[Line.size() - 1] != '\n' && Line[Line.size() - 1] != '\r')); + char *Buf; + if ((Buf = readline(Prompt.c_str())) == nullptr) + return Optional(); - while (!Line.empty() && - (Line[Line.size() - 1] == '\n' || Line[Line.size() - 1] == '\r')) - Line.resize(Line.size() - 1); + Line = Buf; + free(Buf); + + if (!Line.empty()) + add_history(Line.c_str()); return Line; } +#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(std::make_unique()) { + Data->In = In; + Data->Out = Out; +} + +LineEditor::~LineEditor() { ::fwrite("\n", 1, 1, Data->Out); } + +void LineEditor::saveHistory() {} +void LineEditor::loadHistory() {} + +Optional LineEditor::readLine() const { + return PromptOnFILE(Prompt, Data->Out, Data->In); +} #endif // HAVE_LIBEDIT Index: llvm/utils/gn/build/libs/readline/BUILD.gn =================================================================== --- /dev/null +++ llvm/utils/gn/build/libs/readline/BUILD.gn @@ -0,0 +1,12 @@ +import("//llvm/utils/gn/build/libs/readline/enable.gni") + +config("readline_config") { + visibility = [ ":readline" ] + libs = [ "readline" ] +} + +group("readline") { + if (llvm_enable_libreadline) { + public_configs = [ ":readline_config" ] + } +} Index: llvm/utils/gn/build/libs/readline/enable.gni =================================================================== --- /dev/null +++ llvm/utils/gn/build/libs/readline/enable.gni @@ -0,0 +1,3 @@ +declare_args() { + llvm_enable_libreadline = host_os == "linux" +} Index: llvm/utils/gn/secondary/llvm/include/llvm/Config/BUILD.gn =================================================================== --- llvm/utils/gn/secondary/llvm/include/llvm/Config/BUILD.gn +++ llvm/utils/gn/secondary/llvm/include/llvm/Config/BUILD.gn @@ -6,6 +6,7 @@ import("//llvm/utils/gn/build/buildflags.gni") import("//llvm/utils/gn/build/libs/edit/enable.gni") import("//llvm/utils/gn/build/libs/pthread/enable.gni") +import("//llvm/utils/gn/build/libs/readline/enable.gni") import("//llvm/utils/gn/build/libs/terminfo/enable.gni") import("//llvm/utils/gn/build/libs/xar/enable.gni") import("//llvm/utils/gn/build/libs/xml/enable.gni") @@ -296,6 +297,12 @@ values += [ "HAVE_LIBEDIT=" ] } + if (llvm_enable_libreadline) { + values += [ "HAVE_LIBREADLINE=1" ] + } else { + values += [ "HAVE_LIBREADLINE=" ] + } + if (llvm_enable_terminfo) { values += [ "LLVM_ENABLE_TERMINFO=1" ] } else { Index: llvm/utils/gn/secondary/llvm/lib/LineEditor/BUILD.gn =================================================================== --- llvm/utils/gn/secondary/llvm/lib/LineEditor/BUILD.gn +++ llvm/utils/gn/secondary/llvm/lib/LineEditor/BUILD.gn @@ -4,6 +4,7 @@ "//llvm/include/llvm/Config:config", "//llvm/lib/Support", "//llvm/utils/gn/build/libs/edit", + "//llvm/utils/gn/build/libs/readline", ] sources = [ "LineEditor.cpp" ]