Path: blob/main/contrib/llvm-project/llvm/lib/LineEditor/LineEditor.cpp
35232 views
//===-- LineEditor.cpp - line editor --------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "llvm/LineEditor/LineEditor.h"9#include "llvm/ADT/STLExtras.h"10#include "llvm/ADT/SmallString.h"11#include "llvm/Config/config.h"12#include "llvm/Support/Path.h"13#include "llvm/Support/raw_ostream.h"14#include <algorithm>15#include <cassert>16#include <cstdio>17#ifdef HAVE_LIBEDIT18#include <histedit.h>19#endif2021using namespace llvm;2223std::string LineEditor::getDefaultHistoryPath(StringRef ProgName) {24SmallString<32> Path;25if (sys::path::home_directory(Path)) {26sys::path::append(Path, "." + ProgName + "-history");27return std::string(Path);28}29return std::string();30}3132LineEditor::CompleterConcept::~CompleterConcept() = default;33LineEditor::ListCompleterConcept::~ListCompleterConcept() = default;3435std::string LineEditor::ListCompleterConcept::getCommonPrefix(36const std::vector<Completion> &Comps) {37assert(!Comps.empty());3839std::string CommonPrefix = Comps[0].TypedText;40for (const Completion &C : llvm::drop_begin(Comps)) {41size_t Len = std::min(CommonPrefix.size(), C.TypedText.size());42size_t CommonLen = 0;43for (; CommonLen != Len; ++CommonLen) {44if (CommonPrefix[CommonLen] != C.TypedText[CommonLen])45break;46}47CommonPrefix.resize(CommonLen);48}49return CommonPrefix;50}5152LineEditor::CompletionAction53LineEditor::ListCompleterConcept::complete(StringRef Buffer, size_t Pos) const {54CompletionAction Action;55std::vector<Completion> Comps = getCompletions(Buffer, Pos);56if (Comps.empty()) {57Action.Kind = CompletionAction::AK_ShowCompletions;58return Action;59}6061std::string CommonPrefix = getCommonPrefix(Comps);6263// If the common prefix is non-empty we can simply insert it. If there is a64// single completion, this will insert the full completion. If there is more65// than one, this might be enough information to jog the user's memory but if66// not the user can also hit tab again to see the completions because the67// common prefix will then be empty.68if (CommonPrefix.empty()) {69Action.Kind = CompletionAction::AK_ShowCompletions;70for (const Completion &Comp : Comps)71Action.Completions.push_back(Comp.DisplayText);72} else {73Action.Kind = CompletionAction::AK_Insert;74Action.Text = CommonPrefix;75}7677return Action;78}7980LineEditor::CompletionAction LineEditor::getCompletionAction(StringRef Buffer,81size_t Pos) const {82if (!Completer) {83CompletionAction Action;84Action.Kind = CompletionAction::AK_ShowCompletions;85return Action;86}8788return Completer->complete(Buffer, Pos);89}9091#ifdef HAVE_LIBEDIT9293// libedit-based implementation.9495struct LineEditor::InternalData {96LineEditor *LE;9798History *Hist;99EditLine *EL;100101unsigned PrevCount;102std::string ContinuationOutput;103104FILE *Out;105};106107namespace {108109const char *ElGetPromptFn(EditLine *EL) {110LineEditor::InternalData *Data;111if (el_get(EL, EL_CLIENTDATA, &Data) == 0)112return Data->LE->getPrompt().c_str();113return "> ";114}115116// Handles tab completion.117//118// This function is really horrible. But since the alternative is to get into119// the line editor business, here we are.120unsigned char ElCompletionFn(EditLine *EL, int ch) {121LineEditor::InternalData *Data;122if (el_get(EL, EL_CLIENTDATA, &Data) == 0) {123if (!Data->ContinuationOutput.empty()) {124// This is the continuation of the AK_ShowCompletions branch below.125FILE *Out = Data->Out;126127// Print the required output (see below).128::fwrite(Data->ContinuationOutput.c_str(),129Data->ContinuationOutput.size(), 1, Out);130131// Push a sequence of Ctrl-B characters to move the cursor back to its132// original position.133std::string Prevs(Data->PrevCount, '\02');134::el_push(EL, const_cast<char *>(Prevs.c_str()));135136Data->ContinuationOutput.clear();137138return CC_REFRESH;139}140141const LineInfo *LI = ::el_line(EL);142LineEditor::CompletionAction Action = Data->LE->getCompletionAction(143StringRef(LI->buffer, LI->lastchar - LI->buffer),144LI->cursor - LI->buffer);145switch (Action.Kind) {146case LineEditor::CompletionAction::AK_Insert:147::el_insertstr(EL, Action.Text.c_str());148return CC_REFRESH;149150case LineEditor::CompletionAction::AK_ShowCompletions:151if (Action.Completions.empty()) {152return CC_REFRESH_BEEP;153} else {154// Push a Ctrl-E and a tab. The Ctrl-E causes libedit to move the cursor155// to the end of the line, so that when we emit a newline we will be on156// a new blank line. The tab causes libedit to call this function again157// after moving the cursor. There doesn't seem to be anything we can do158// from here to cause libedit to move the cursor immediately. This will159// break horribly if the user has rebound their keys, so for now we do160// not permit user rebinding.161::el_push(EL, const_cast<char *>("\05\t"));162163// This assembles the output for the continuation block above.164raw_string_ostream OS(Data->ContinuationOutput);165166// Move cursor to a blank line.167OS << "\n";168169// Emit the completions.170for (const std::string &Completion : Action.Completions)171OS << Completion << "\n";172173// Fool libedit into thinking nothing has changed. Reprint its prompt174// and the user input. Note that the cursor will remain at the end of175// the line after this.176OS << Data->LE->getPrompt()177<< StringRef(LI->buffer, LI->lastchar - LI->buffer);178179// This is the number of characters we need to tell libedit to go back:180// the distance between end of line and the original cursor position.181Data->PrevCount = LI->lastchar - LI->cursor;182183return CC_REFRESH;184}185}186}187return CC_ERROR;188}189190} // end anonymous namespace191192LineEditor::LineEditor(StringRef ProgName, StringRef HistoryPath, FILE *In,193FILE *Out, FILE *Err)194: Prompt((ProgName + "> ").str()), HistoryPath(std::string(HistoryPath)),195Data(new InternalData) {196if (HistoryPath.empty())197this->HistoryPath = getDefaultHistoryPath(ProgName);198199Data->LE = this;200Data->Out = Out;201202Data->Hist = ::history_init();203assert(Data->Hist);204205Data->EL = ::el_init(ProgName.str().c_str(), In, Out, Err);206assert(Data->EL);207208::el_set(Data->EL, EL_PROMPT, ElGetPromptFn);209::el_set(Data->EL, EL_EDITOR, "emacs");210::el_set(Data->EL, EL_HIST, history, Data->Hist);211::el_set(Data->EL, EL_ADDFN, "tab_complete", "Tab completion function",212ElCompletionFn);213::el_set(Data->EL, EL_BIND, "\t", "tab_complete", NULL);214::el_set(Data->EL, EL_BIND, "^r", "em-inc-search-prev",215NULL); // Cycle through backwards search, entering string216::el_set(Data->EL, EL_BIND, "^w", "ed-delete-prev-word",217NULL); // Delete previous word, behave like bash does.218::el_set(Data->EL, EL_BIND, "\033[3~", "ed-delete-next-char",219NULL); // Fix the delete key.220::el_set(Data->EL, EL_CLIENTDATA, Data.get());221222HistEvent HE;223::history(Data->Hist, &HE, H_SETSIZE, 800);224::history(Data->Hist, &HE, H_SETUNIQUE, 1);225loadHistory();226}227228LineEditor::~LineEditor() {229saveHistory();230231::history_end(Data->Hist);232::el_end(Data->EL);233::fwrite("\n", 1, 1, Data->Out);234}235236void LineEditor::saveHistory() {237if (!HistoryPath.empty()) {238HistEvent HE;239::history(Data->Hist, &HE, H_SAVE, HistoryPath.c_str());240}241}242243void LineEditor::loadHistory() {244if (!HistoryPath.empty()) {245HistEvent HE;246::history(Data->Hist, &HE, H_LOAD, HistoryPath.c_str());247}248}249250std::optional<std::string> LineEditor::readLine() const {251// Call el_gets to prompt the user and read the user's input.252int LineLen = 0;253const char *Line = ::el_gets(Data->EL, &LineLen);254255// Either of these may mean end-of-file.256if (!Line || LineLen == 0)257return std::nullopt;258259// Strip any newlines off the end of the string.260while (LineLen > 0 &&261(Line[LineLen - 1] == '\n' || Line[LineLen - 1] == '\r'))262--LineLen;263264HistEvent HE;265if (LineLen > 0)266::history(Data->Hist, &HE, H_ENTER, Line);267268return std::string(Line, LineLen);269}270271#else // HAVE_LIBEDIT272273// Simple fgets-based implementation.274275struct LineEditor::InternalData {276FILE *In;277FILE *Out;278};279280LineEditor::LineEditor(StringRef ProgName, StringRef HistoryPath, FILE *In,281FILE *Out, FILE *Err)282: Prompt((ProgName + "> ").str()), Data(new InternalData) {283Data->In = In;284Data->Out = Out;285}286287LineEditor::~LineEditor() {288::fwrite("\n", 1, 1, Data->Out);289}290291void LineEditor::saveHistory() {}292void LineEditor::loadHistory() {}293294std::optional<std::string> LineEditor::readLine() const {295::fprintf(Data->Out, "%s", Prompt.c_str());296297std::string Line;298do {299char Buf[64];300char *Res = ::fgets(Buf, sizeof(Buf), Data->In);301if (!Res) {302if (Line.empty())303return std::nullopt;304else305return Line;306}307Line.append(Buf);308} while (Line.empty() ||309(Line[Line.size() - 1] != '\n' && Line[Line.size() - 1] != '\r'));310311while (!Line.empty() &&312(Line[Line.size() - 1] == '\n' || Line[Line.size() - 1] == '\r'))313Line.resize(Line.size() - 1);314315return Line;316}317318#endif // HAVE_LIBEDIT319320321