brintos

brintos / llvm-project-archived public Read only

0
0
Text · 60.2 KiB · e2995b3 Raw
1750 lines · cpp
1//===-- Editline.cpp ------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include <climits>10#include <iomanip>11#include <optional>12 13#include "lldb/Host/Editline.h"14#include "lldb/Host/HostInfo.h"15#include "lldb/Host/StreamFile.h"16#include "lldb/Utility/AnsiTerminal.h"17#include "lldb/Utility/CompletionRequest.h"18#include "lldb/Utility/FileSpec.h"19#include "lldb/Utility/LLDBAssert.h"20#include "lldb/Utility/SelectHelper.h"21#include "lldb/Utility/Status.h"22#include "lldb/Utility/StreamString.h"23#include "lldb/Utility/StringList.h"24#include "lldb/Utility/Timeout.h"25#include "lldb/lldb-forward.h"26#include "llvm/Support/ConvertUTF.h"27 28#include "llvm/Support/FileSystem.h"29#include "llvm/Support/Locale.h"30#include "llvm/Support/Threading.h"31 32using namespace lldb_private;33using namespace lldb_private::line_editor;34 35// Editline uses careful cursor management to achieve the illusion of editing a36// multi-line block of text with a single line editor.  Preserving this37// illusion requires fairly careful management of cursor state.  Read and38// understand the relationship between DisplayInput(), MoveCursor(),39// SetCurrentLine(), and SaveEditedLine() before making changes.40 41/// https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf42#define ESCAPE "\x1b"43#define ANSI_CLEAR_BELOW ESCAPE "[J"44#define ANSI_CLEAR_RIGHT ESCAPE "[K"45#define ANSI_SET_COLUMN_N ESCAPE "[%dG"46#define ANSI_UP_N_ROWS ESCAPE "[%dA"47#define ANSI_DOWN_N_ROWS ESCAPE "[%dB"48 49#if LLDB_EDITLINE_USE_WCHAR50 51#define EditLineConstString(str) L##str52#define EditLineStringFormatSpec "%ls"53 54#else55 56#define EditLineConstString(str) str57#define EditLineStringFormatSpec "%s"58 59// use #defines so wide version functions and structs will resolve to old60// versions for case of libedit not built with wide char support61#define history_w history62#define history_winit history_init63#define history_wend history_end64#define HistoryW History65#define HistEventW HistEvent66#define LineInfoW LineInfo67 68#define el_wgets el_gets69#define el_wgetc el_getc70#define el_wpush el_push71#define el_wparse el_parse72#define el_wset el_set73#define el_wget el_get74#define el_wline el_line75#define el_winsertstr el_insertstr76#define el_wdeletestr el_deletestr77 78#endif // #if LLDB_EDITLINE_USE_WCHAR79 80template <typename T> class ScopedOptional {81public:82  template <typename... Args>83  ScopedOptional(std::optional<T> &optional, Args &&...args)84      : m_optional(optional) {85    m_optional.emplace(std::forward<Args>(args)...);86  }87  ~ScopedOptional() { m_optional.reset(); }88 89private:90  std::optional<T> &m_optional;91};92 93bool IsOnlySpaces(const EditLineStringType &content) {94  for (wchar_t ch : content) {95    if (ch != EditLineCharType(' '))96      return false;97  }98  return true;99}100 101static int GetOperation(HistoryOperation op) {102  // The naming used by editline for the history operations is counter103  // intuitive to how it's used in LLDB's editline implementation.104  //105  //  - The H_LAST returns the oldest entry in the history.106  //107  //  - The H_PREV operation returns the previous element in the history, which108  //    is newer than the current one.109  //110  //  - The H_CURR returns the current entry in the history.111  //112  //  - The H_NEXT operation returns the next element in the history, which is113  //    older than the current one.114  //115  //  - The H_FIRST returns the most recent entry in the history.116  //117  // The naming of the enum entries match the semantic meaning.118  switch (op) {119  case HistoryOperation::Oldest:120    return H_LAST;121  case HistoryOperation::Older:122    return H_NEXT;123  case HistoryOperation::Current:124    return H_CURR;125  case HistoryOperation::Newer:126    return H_PREV;127  case HistoryOperation::Newest:128    return H_FIRST;129  }130  llvm_unreachable("Fully covered switch!");131}132 133EditLineStringType CombineLines(const std::vector<EditLineStringType> &lines) {134  EditLineStringStreamType combined_stream;135  for (EditLineStringType line : lines) {136    combined_stream << line.c_str() << "\n";137  }138  return combined_stream.str();139}140 141std::vector<EditLineStringType> SplitLines(const EditLineStringType &input) {142  std::vector<EditLineStringType> result;143  size_t start = 0;144  while (start < input.length()) {145    size_t end = input.find('\n', start);146    if (end == std::string::npos) {147      result.push_back(input.substr(start));148      break;149    }150    result.push_back(input.substr(start, end - start));151    start = end + 1;152  }153  // Treat an empty history session as a single command of zero-length instead154  // of returning an empty vector.155  if (result.empty()) {156    result.emplace_back();157  }158  return result;159}160 161EditLineStringType FixIndentation(const EditLineStringType &line,162                                  int indent_correction) {163  if (indent_correction == 0)164    return line;165  if (indent_correction < 0)166    return line.substr(-indent_correction);167  return EditLineStringType(indent_correction, EditLineCharType(' ')) + line;168}169 170int GetIndentation(const EditLineStringType &line) {171  int space_count = 0;172  for (EditLineCharType ch : line) {173    if (ch != EditLineCharType(' '))174      break;175    ++space_count;176  }177  return space_count;178}179 180bool IsInputPending(FILE *file) {181  // FIXME: This will be broken on Windows if we ever re-enable Editline.  You182  // can't use select183  // on something that isn't a socket.  This will have to be re-written to not184  // use a FILE*, but instead use some kind of yet-to-be-created abstraction185  // that select-like functionality on non-socket objects.186  const int fd = fileno(file);187  SelectHelper select_helper;188  select_helper.SetTimeout(std::chrono::microseconds(0));189  select_helper.FDSetRead(fd);190  return select_helper.Select().Success();191}192 193namespace lldb_private {194namespace line_editor {195typedef std::weak_ptr<EditlineHistory> EditlineHistoryWP;196 197// EditlineHistory objects are sometimes shared between multiple Editline198// instances with the same program name.199 200class EditlineHistory {201private:202  // Use static GetHistory() function to get a EditlineHistorySP to one of203  // these objects204  EditlineHistory(const std::string &prefix, uint32_t size, bool unique_entries)205      : m_prefix(prefix) {206    m_history = history_winit();207    history_w(m_history, &m_event, H_SETSIZE, size);208    if (unique_entries)209      history_w(m_history, &m_event, H_SETUNIQUE, 1);210  }211 212  const char *GetHistoryFilePath() {213    // Compute the history path lazily.214    if (m_path.empty() && m_history && !m_prefix.empty()) {215      FileSpec lldb_dir = HostInfo::GetUserLLDBDir();216 217      // LLDB stores its history in ~/.lldb/. If for some reason this directory218      // isn't writable or cannot be created, history won't be available.219      if (!llvm::sys::fs::create_directory(lldb_dir.GetPath())) {220#if LLDB_EDITLINE_USE_WCHAR221        std::string filename = m_prefix + "-widehistory";222#else223        std::string filename = m_prefix + "-history";224#endif225        FileSpec lldb_history_file =226            lldb_dir.CopyByAppendingPathComponent(filename);227        m_path = lldb_history_file.GetPath();228      }229    }230 231    if (m_path.empty())232      return nullptr;233 234    return m_path.c_str();235  }236 237public:238  ~EditlineHistory() {239    Save();240 241    if (m_history) {242      history_wend(m_history);243      m_history = nullptr;244    }245  }246 247  static EditlineHistorySP GetHistory(const std::string &prefix) {248    typedef std::map<std::string, EditlineHistoryWP> WeakHistoryMap;249    static std::recursive_mutex g_mutex;250    static WeakHistoryMap g_weak_map;251    std::lock_guard<std::recursive_mutex> guard(g_mutex);252    WeakHistoryMap::const_iterator pos = g_weak_map.find(prefix);253    EditlineHistorySP history_sp;254    if (pos != g_weak_map.end()) {255      history_sp = pos->second.lock();256      if (history_sp)257        return history_sp;258      g_weak_map.erase(pos);259    }260    history_sp.reset(new EditlineHistory(prefix, 800, true));261    g_weak_map[prefix] = history_sp;262    return history_sp;263  }264 265  bool IsValid() const { return m_history != nullptr; }266 267  HistoryW *GetHistoryPtr() { return m_history; }268 269  void Enter(const EditLineCharType *line_cstr) {270    if (m_history)271      history_w(m_history, &m_event, H_ENTER, line_cstr);272  }273 274  bool Load() {275    if (m_history) {276      const char *path = GetHistoryFilePath();277      if (path) {278        history_w(m_history, &m_event, H_LOAD, path);279        return true;280      }281    }282    return false;283  }284 285  bool Save() {286    if (m_history) {287      const char *path = GetHistoryFilePath();288      if (path) {289        history_w(m_history, &m_event, H_SAVE, path);290        return true;291      }292    }293    return false;294  }295 296protected:297  /// The history object.298  HistoryW *m_history = nullptr;299  /// The history event needed to contain all history events.300  HistEventW m_event;301  /// The prefix name (usually the editline program name) to use when302  /// loading/saving history.303  std::string m_prefix;304  /// Path to the history file.305  std::string m_path;306};307} // namespace line_editor308} // namespace lldb_private309 310// Editline private methods311 312void Editline::SetBaseLineNumber(int line_number) {313  m_base_line_number = line_number;314  m_line_number_digits =315      std::max<int>(3, std::to_string(line_number).length() + 1);316}317 318std::string Editline::PromptForIndex(int line_index) {319  bool use_line_numbers = m_multiline_enabled && m_base_line_number > 0;320  std::string prompt = m_set_prompt;321  if (use_line_numbers && prompt.length() == 0)322    prompt = ": ";323  std::string continuation_prompt = prompt;324  if (m_set_continuation_prompt.length() > 0) {325    continuation_prompt = m_set_continuation_prompt;326    // Ensure that both prompts are the same length through space padding327    const size_t prompt_width = ansi::ColumnWidth(prompt);328    const size_t cont_prompt_width = ansi::ColumnWidth(continuation_prompt);329    const size_t padded_prompt_width =330        std::max(prompt_width, cont_prompt_width);331    if (prompt_width < padded_prompt_width)332      prompt += std::string(padded_prompt_width - prompt_width, ' ');333    else if (cont_prompt_width < padded_prompt_width)334      continuation_prompt +=335          std::string(padded_prompt_width - cont_prompt_width, ' ');336  }337 338  if (use_line_numbers) {339    StreamString prompt_stream;340    prompt_stream.Printf(341        "%*d%s", m_line_number_digits, m_base_line_number + line_index,342        (line_index == 0) ? prompt.c_str() : continuation_prompt.c_str());343    return std::string(std::move(prompt_stream.GetString()));344  }345  return (line_index == 0) ? prompt : continuation_prompt;346}347 348void Editline::SetCurrentLine(int line_index) {349  m_current_line_index = line_index;350  m_current_prompt = PromptForIndex(line_index);351}352 353size_t Editline::GetPromptWidth() {354  return ansi::ColumnWidth(PromptForIndex(0));355}356 357bool Editline::IsEmacs() {358  const char *editor;359  el_get(m_editline, EL_EDITOR, &editor);360  return editor[0] == 'e';361}362 363bool Editline::IsOnlySpaces() {364  const LineInfoW *info = el_wline(m_editline);365  for (const EditLineCharType *character = info->buffer;366       character < info->lastchar; character++) {367    if (*character != ' ')368      return false;369  }370  return true;371}372 373int Editline::GetLineIndexForLocation(CursorLocation location, int cursor_row) {374  int line = 0;375  if (location == CursorLocation::EditingPrompt ||376      location == CursorLocation::BlockEnd ||377      location == CursorLocation::EditingCursor) {378    for (unsigned index = 0; index < m_current_line_index; index++) {379      line += CountRowsForLine(m_input_lines[index]);380    }381    if (location == CursorLocation::EditingCursor) {382      line += cursor_row;383    } else if (location == CursorLocation::BlockEnd) {384      for (unsigned index = m_current_line_index; index < m_input_lines.size();385           index++) {386        line += CountRowsForLine(m_input_lines[index]);387      }388      --line;389    }390  }391  return line;392}393 394void Editline::MoveCursor(CursorLocation from, CursorLocation to) {395  const LineInfoW *info = el_wline(m_editline);396  int editline_cursor_position =397      (int)((info->cursor - info->buffer) + GetPromptWidth());398  int editline_cursor_row = editline_cursor_position / m_terminal_width;399 400  LockedStreamFile locked_stream = m_output_stream_sp->Lock();401 402  // Determine relative starting and ending lines403  int fromLine = GetLineIndexForLocation(from, editline_cursor_row);404  int toLine = GetLineIndexForLocation(to, editline_cursor_row);405  if (toLine != fromLine) {406    fprintf(locked_stream.GetFile().GetStream(),407            (toLine > fromLine) ? ANSI_DOWN_N_ROWS : ANSI_UP_N_ROWS,408            std::abs(toLine - fromLine));409  }410 411  // Determine target column412  int toColumn = 1;413  if (to == CursorLocation::EditingCursor) {414    toColumn =415        editline_cursor_position - (editline_cursor_row * m_terminal_width) + 1;416  } else if (to == CursorLocation::BlockEnd && !m_input_lines.empty()) {417    toColumn =418        ((m_input_lines[m_input_lines.size() - 1].length() + GetPromptWidth()) %419         80) +420        1;421  }422  fprintf(locked_stream.GetFile().GetStream(), ANSI_SET_COLUMN_N, toColumn);423}424 425void Editline::DisplayInput(int firstIndex) {426  LockedStreamFile locked_stream = m_output_stream_sp->Lock();427  fprintf(locked_stream.GetFile().GetStream(),428          ANSI_SET_COLUMN_N ANSI_CLEAR_BELOW, 1);429  int line_count = (int)m_input_lines.size();430  for (int index = firstIndex; index < line_count; index++) {431    fprintf(locked_stream.GetFile().GetStream(),432            "%s"433            "%s"434            "%s" EditLineStringFormatSpec " ",435            m_prompt_ansi_prefix.c_str(), PromptForIndex(index).c_str(),436            m_prompt_ansi_suffix.c_str(), m_input_lines[index].c_str());437    if (index < line_count - 1)438      fprintf(locked_stream.GetFile().GetStream(), "\n");439  }440}441 442int Editline::CountRowsForLine(const EditLineStringType &content) {443  std::string prompt =444      PromptForIndex(0); // Prompt width is constant during an edit session445  int line_length = (int)(content.length() + ansi::ColumnWidth(prompt));446  return (line_length / m_terminal_width) + 1;447}448 449void Editline::SaveEditedLine() {450  const LineInfoW *info = el_wline(m_editline);451  m_input_lines[m_current_line_index] =452      EditLineStringType(info->buffer, info->lastchar - info->buffer);453}454 455StringList Editline::GetInputAsStringList(int line_count) {456  StringList lines;457  for (EditLineStringType line : m_input_lines) {458    if (line_count == 0)459      break;460#if LLDB_EDITLINE_USE_WCHAR461    std::string buffer;462    llvm::convertWideToUTF8(line, buffer);463    lines.AppendString(buffer);464#else465    lines.AppendString(line);466#endif467    --line_count;468  }469  return lines;470}471 472unsigned char Editline::RecallHistory(HistoryOperation op) {473  assert(op == HistoryOperation::Older || op == HistoryOperation::Newer);474  if (!m_history_sp || !m_history_sp->IsValid())475    return CC_ERROR;476 477  HistoryW *pHistory = m_history_sp->GetHistoryPtr();478  HistEventW history_event;479  std::vector<EditLineStringType> new_input_lines;480 481  // Treat moving from the "live" entry differently482  if (!m_in_history) {483    switch (op) {484    case HistoryOperation::Newer:485      return CC_ERROR; // Can't go newer than the "live" entry486    case HistoryOperation::Older: {487      if (history_w(pHistory, &history_event,488                    GetOperation(HistoryOperation::Newest)) == -1)489        return CC_ERROR;490      // Save any edits to the "live" entry in case we return by moving forward491      // in history (it would be more bash-like to save over any current entry,492      // but libedit doesn't offer the ability to add entries anywhere except493      // the end.)494      SaveEditedLine();495      m_live_history_lines = m_input_lines;496      m_in_history = true;497    } break;498    default:499      llvm_unreachable("unsupported history direction");500    }501  } else {502    if (history_w(pHistory, &history_event, GetOperation(op)) == -1) {503      switch (op) {504      case HistoryOperation::Older:505        // Can't move earlier than the earliest entry.506        return CC_ERROR;507      case HistoryOperation::Newer:508        // Moving to newer-than-the-newest entry yields the "live" entry.509        new_input_lines = m_live_history_lines;510        m_in_history = false;511        break;512      default:513        llvm_unreachable("unsupported history direction");514      }515    }516  }517 518  // If we're pulling the lines from history, split them apart519  if (m_in_history)520    new_input_lines = SplitLines(history_event.str);521 522  // Erase the current edit session and replace it with a new one523  MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);524  m_input_lines = new_input_lines;525  DisplayInput();526 527  // Prepare to edit the last line when moving to previous entry, or the first528  // line when moving to next entry529  switch (op) {530  case HistoryOperation::Older:531    m_current_line_index = (int)m_input_lines.size() - 1;532    break;533  case HistoryOperation::Newer:534    m_current_line_index = 0;535    break;536  default:537    llvm_unreachable("unsupported history direction");538  }539  SetCurrentLine(m_current_line_index);540  MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt);541  return CC_NEWLINE;542}543 544int Editline::GetCharacter(EditLineGetCharType *c) {545  const LineInfoW *info = el_wline(m_editline);546 547  // Paint a ANSI formatted version of the desired prompt over the version548  // libedit draws. (will only be requested if colors are supported)549  if (m_needs_prompt_repaint) {550    ScopedOptional<LockedStreamFile> scope(m_locked_output,551                                           m_output_stream_sp->Lock());552    MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);553    fprintf(m_locked_output->GetFile().GetStream(),554            "%s"555            "%s"556            "%s",557            m_prompt_ansi_prefix.c_str(), Prompt(),558            m_prompt_ansi_suffix.c_str());559    MoveCursor(CursorLocation::EditingPrompt, CursorLocation::EditingCursor);560    m_needs_prompt_repaint = false;561  }562 563  if (m_multiline_enabled) {564    // Detect when the number of rows used for this input line changes due to565    // an edit566    int lineLength = (int)((info->lastchar - info->buffer) + GetPromptWidth());567    int new_line_rows = (lineLength / m_terminal_width) + 1;568    if (m_current_line_rows != -1 && new_line_rows != m_current_line_rows) {569      // Respond by repainting the current state from this line on570      MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);571      SaveEditedLine();572      DisplayInput(m_current_line_index);573      MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);574    }575    m_current_line_rows = new_line_rows;576  }577 578  if (m_terminal_size_has_changed)579    ApplyTerminalSizeChange();580 581  // This mutex is locked by our caller (GetLine). Unlock it while we read a582  // character (blocking operation), so we do not hold the mutex583  // indefinitely. This gives a chance for someone to interrupt us. After584  // Read returns, immediately lock the mutex again and check if we were585  // interrupted.586  m_locked_output.reset();587 588  if (m_redraw_callback)589    m_redraw_callback();590 591  // Read an actual character592  lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess;593  char ch = 0;594  int read_count =595      m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr);596 597  // Re-lock the output mutex to protected m_editor_status here and in the598  // switch below.599  m_locked_output.emplace(m_output_stream_sp->Lock());600  if (m_editor_status == EditorStatus::Interrupted) {601    while (read_count > 0 && status == lldb::eConnectionStatusSuccess)602      read_count =603          m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr);604    lldbassert(status == lldb::eConnectionStatusInterrupted);605    return 0;606  }607 608  if (read_count) {609    if (CompleteCharacter(ch, *c))610      return 1;611    return 0;612  }613 614  switch (status) {615  case lldb::eConnectionStatusSuccess:616    llvm_unreachable("Success should have resulted in positive read_count.");617  case lldb::eConnectionStatusInterrupted:618    llvm_unreachable("Interrupts should have been handled above.");619  case lldb::eConnectionStatusError:620  case lldb::eConnectionStatusTimedOut:621  case lldb::eConnectionStatusEndOfFile:622  case lldb::eConnectionStatusNoConnection:623  case lldb::eConnectionStatusLostConnection:624    m_editor_status = EditorStatus::EndOfInput;625  }626 627  return 0;628}629 630const char *Editline::Prompt() {631  if (m_color)632    m_needs_prompt_repaint = true;633  return m_current_prompt.c_str();634}635 636unsigned char Editline::BreakLineCommand(int ch) {637  // Preserve any content beyond the cursor, truncate and save the current line638  const LineInfoW *info = el_wline(m_editline);639  auto current_line =640      EditLineStringType(info->buffer, info->cursor - info->buffer);641  auto new_line_fragment =642      EditLineStringType(info->cursor, info->lastchar - info->cursor);643  m_input_lines[m_current_line_index] = current_line;644 645  // Ignore whitespace-only extra fragments when breaking a line646  if (::IsOnlySpaces(new_line_fragment))647    new_line_fragment = EditLineConstString("");648 649  // Establish the new cursor position at the start of a line when inserting a650  // line break651  m_revert_cursor_index = 0;652 653  // Don't perform automatic formatting when pasting654  if (!IsInputPending(m_input_file)) {655    // Apply smart indentation656    if (m_fix_indentation_callback) {657      StringList lines = GetInputAsStringList(m_current_line_index + 1);658#if LLDB_EDITLINE_USE_WCHAR659      std::string buffer;660      llvm::convertWideToUTF8(new_line_fragment, buffer);661      lines.AppendString(buffer);662#else663      lines.AppendString(new_line_fragment);664#endif665 666      int indent_correction = m_fix_indentation_callback(this, lines, 0);667      new_line_fragment = FixIndentation(new_line_fragment, indent_correction);668      m_revert_cursor_index = GetIndentation(new_line_fragment);669    }670  }671 672  // Insert the new line and repaint everything from the split line on down673  m_input_lines.insert(m_input_lines.begin() + m_current_line_index + 1,674                       new_line_fragment);675  MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);676  DisplayInput(m_current_line_index);677 678  // Reposition the cursor to the right line and prepare to edit the new line679  SetCurrentLine(m_current_line_index + 1);680  MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt);681  return CC_NEWLINE;682}683 684unsigned char Editline::EndOrAddLineCommand(int ch) {685  // Don't perform end of input detection when pasting, always treat this as a686  // line break687  if (IsInputPending(m_input_file)) {688    return BreakLineCommand(ch);689  }690 691  // Save any edits to this line692  SaveEditedLine();693 694  // If this is the end of the last line, consider whether to add a line695  // instead696  const LineInfoW *info = el_wline(m_editline);697  if (m_current_line_index == m_input_lines.size() - 1 &&698      info->cursor == info->lastchar) {699    if (m_is_input_complete_callback) {700      auto lines = GetInputAsStringList();701      if (!m_is_input_complete_callback(this, lines)) {702        return BreakLineCommand(ch);703      }704 705      // The completion test is allowed to change the input lines when complete706      m_input_lines.clear();707      for (unsigned index = 0; index < lines.GetSize(); index++) {708#if LLDB_EDITLINE_USE_WCHAR709        std::wstring wbuffer;710        llvm::ConvertUTF8toWide(lines[index], wbuffer);711        m_input_lines.insert(m_input_lines.end(), wbuffer);712#else713        m_input_lines.insert(m_input_lines.end(), lines[index]);714#endif715      }716    }717  }718  MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockEnd);719  LockedStreamFile locked_stream = m_output_stream_sp->Lock();720  fprintf(locked_stream.GetFile().GetStream(), "\n");721  m_editor_status = EditorStatus::Complete;722  return CC_NEWLINE;723}724 725unsigned char Editline::DeleteNextCharCommand(int ch) {726  LockedStreamFile locked_stream = m_output_stream_sp->Lock();727  LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline));728 729  // Just delete the next character normally if possible730  if (info->cursor < info->lastchar) {731    info->cursor++;732    el_deletestr(m_editline, 1);733    return CC_REFRESH;734  }735 736  // Fail when at the end of the last line, except when ^D is pressed on the737  // line is empty, in which case it is treated as EOF738  if (m_current_line_index == m_input_lines.size() - 1) {739    if (ch == 4 && info->buffer == info->lastchar) {740      fprintf(locked_stream.GetFile().GetStream(), "^D\n");741      m_editor_status = EditorStatus::EndOfInput;742      return CC_EOF;743    }744    return CC_ERROR;745  }746 747  // Prepare to combine this line with the one below748  MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);749 750  // Insert the next line of text at the cursor and restore the cursor position751  const EditLineCharType *cursor = info->cursor;752  el_winsertstr(m_editline, m_input_lines[m_current_line_index + 1].c_str());753  info->cursor = cursor;754  SaveEditedLine();755 756  // Delete the extra line757  m_input_lines.erase(m_input_lines.begin() + m_current_line_index + 1);758 759  // Clear and repaint from this line on down760  DisplayInput(m_current_line_index);761  MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);762  return CC_REFRESH;763}764 765unsigned char Editline::DeletePreviousCharCommand(int ch) {766  LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline));767 768  // Just delete the previous character normally when not at the start of a769  // line770  if (info->cursor > info->buffer) {771    el_deletestr(m_editline, 1);772    return CC_REFRESH;773  }774 775  // No prior line and no prior character?  Let the user know776  if (m_current_line_index == 0)777    return CC_ERROR;778 779  // No prior character, but prior line?  Combine with the line above780  SaveEditedLine();781  SetCurrentLine(m_current_line_index - 1);782  auto priorLine = m_input_lines[m_current_line_index];783  m_input_lines.erase(m_input_lines.begin() + m_current_line_index);784  m_input_lines[m_current_line_index] =785      priorLine + m_input_lines[m_current_line_index];786 787  // Repaint from the new line down788  LockedStreamFile locked_stream = m_output_stream_sp->Lock();789  fprintf(locked_stream.GetFile().GetStream(), ANSI_UP_N_ROWS ANSI_SET_COLUMN_N,790          CountRowsForLine(priorLine), 1);791  DisplayInput(m_current_line_index);792 793  // Put the cursor back where libedit expects it to be before returning to794  // editing by telling libedit about the newly inserted text795  MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt);796  el_winsertstr(m_editline, priorLine.c_str());797  return CC_REDISPLAY;798}799 800unsigned char Editline::PreviousLineCommand(int ch) {801  SaveEditedLine();802 803  if (m_current_line_index == 0) {804    return RecallHistory(HistoryOperation::Older);805  }806 807  LockedStreamFile locked_stream = m_output_stream_sp->Lock();808 809  // Start from a known location810  MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);811 812  // Treat moving up from a blank last line as a deletion of that line813  if (m_current_line_index == m_input_lines.size() - 1 && IsOnlySpaces()) {814    m_input_lines.erase(m_input_lines.begin() + m_current_line_index);815    fprintf(locked_stream.GetFile().GetStream(), ANSI_CLEAR_BELOW);816  }817 818  SetCurrentLine(m_current_line_index - 1);819  fprintf(locked_stream.GetFile().GetStream(), ANSI_UP_N_ROWS ANSI_SET_COLUMN_N,820          CountRowsForLine(m_input_lines[m_current_line_index]), 1);821  return CC_NEWLINE;822}823 824unsigned char Editline::NextLineCommand(int ch) {825  SaveEditedLine();826 827  // Handle attempts to move down from the last line828  if (m_current_line_index == m_input_lines.size() - 1) {829    // Don't add an extra line if the existing last line is blank, move through830    // history instead831    if (IsOnlySpaces()) {832      return RecallHistory(HistoryOperation::Newer);833    }834 835    // Determine indentation for the new line836    int indentation = 0;837    if (m_fix_indentation_callback) {838      StringList lines = GetInputAsStringList();839      lines.AppendString("");840      indentation = m_fix_indentation_callback(this, lines, 0);841    }842    m_input_lines.insert(843        m_input_lines.end(),844        EditLineStringType(indentation, EditLineCharType(' ')));845  }846 847  // Move down past the current line using newlines to force scrolling if848  // needed849  SetCurrentLine(m_current_line_index + 1);850  const LineInfoW *info = el_wline(m_editline);851  int cursor_position = (int)((info->cursor - info->buffer) + GetPromptWidth());852  int cursor_row = cursor_position / m_terminal_width;853 854  LockedStreamFile locked_stream = m_output_stream_sp->Lock();855  for (int line_count = 0; line_count < m_current_line_rows - cursor_row;856       line_count++) {857    fprintf(locked_stream.GetFile().GetStream(), "\n");858  }859  return CC_NEWLINE;860}861 862unsigned char Editline::PreviousHistoryCommand(int ch) {863  SaveEditedLine();864 865  return RecallHistory(HistoryOperation::Older);866}867 868unsigned char Editline::NextHistoryCommand(int ch) {869  SaveEditedLine();870 871  return RecallHistory(HistoryOperation::Newer);872}873 874unsigned char Editline::FixIndentationCommand(int ch) {875  if (!m_fix_indentation_callback)876    return CC_NORM;877 878  // Insert the character typed before proceeding879  EditLineCharType inserted[] = {(EditLineCharType)ch, 0};880  el_winsertstr(m_editline, inserted);881  LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline));882  int cursor_position = info->cursor - info->buffer;883 884  // Save the edits and determine the correct indentation level885  SaveEditedLine();886  StringList lines = GetInputAsStringList(m_current_line_index + 1);887  int indent_correction =888      m_fix_indentation_callback(this, lines, cursor_position);889 890  // If it is already correct no special work is needed891  if (indent_correction == 0)892    return CC_REFRESH;893 894  // Change the indentation level of the line895  std::string currentLine = lines.GetStringAtIndex(m_current_line_index);896  if (indent_correction > 0) {897    currentLine = currentLine.insert(0, indent_correction, ' ');898  } else {899    currentLine = currentLine.erase(0, -indent_correction);900  }901#if LLDB_EDITLINE_USE_WCHAR902  std::wstring wbuffer;903  llvm::ConvertUTF8toWide(currentLine, wbuffer);904  m_input_lines[m_current_line_index] = wbuffer;905#else906  m_input_lines[m_current_line_index] = currentLine;907#endif908 909  // Update the display to reflect the change910  MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);911  DisplayInput(m_current_line_index);912 913  // Reposition the cursor back on the original line and prepare to restart914  // editing with a new cursor position915  SetCurrentLine(m_current_line_index);916  MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt);917  m_revert_cursor_index = cursor_position + indent_correction;918  return CC_NEWLINE;919}920 921unsigned char Editline::RevertLineCommand(int ch) {922  el_winsertstr(m_editline, m_input_lines[m_current_line_index].c_str());923  if (m_revert_cursor_index >= 0) {924    LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline));925    info->cursor = info->buffer + m_revert_cursor_index;926    if (info->cursor > info->lastchar) {927      info->cursor = info->lastchar;928    }929    m_revert_cursor_index = -1;930  }931  return CC_REFRESH;932}933 934unsigned char Editline::BufferStartCommand(int ch) {935  SaveEditedLine();936  MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);937  SetCurrentLine(0);938  m_revert_cursor_index = 0;939  return CC_NEWLINE;940}941 942unsigned char Editline::BufferEndCommand(int ch) {943  SaveEditedLine();944  MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockEnd);945  SetCurrentLine((int)m_input_lines.size() - 1);946  MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt);947  return CC_NEWLINE;948}949 950/// Prints completions and their descriptions to the given file. Only the951/// completions in the interval [start, end) are printed.952static size_t953PrintCompletion(FILE *output_file,954                llvm::ArrayRef<CompletionResult::Completion> results,955                size_t max_completion_length, size_t max_length,956                std::optional<size_t> max_height = std::nullopt) {957  constexpr size_t ellipsis_length = 3;958  constexpr size_t padding_length = 8;959  constexpr size_t separator_length = 4;960 961  const size_t description_col =962      std::min(max_completion_length + padding_length, max_length);963 964  size_t lines_printed = 0;965  size_t results_printed = 0;966  for (const CompletionResult::Completion &c : results) {967    if (max_height && lines_printed >= *max_height)968      break;969 970    results_printed++;971 972    if (c.GetCompletion().empty())973      continue;974 975    // Print the leading padding.976    fprintf(output_file, "        ");977 978    // Print the completion with trailing padding to the description column if979    // that fits on the screen. Otherwise print whatever fits on the screen980    // followed by ellipsis.981    const size_t completion_length = c.GetCompletion().size();982    if (padding_length + completion_length < max_length) {983      fprintf(output_file, "%-*s",984              static_cast<int>(description_col - padding_length),985              c.GetCompletion().c_str());986    } else {987      // If the completion doesn't fit on the screen, print ellipsis and don't988      // bother with the description.989      fprintf(output_file, "%.*s...\n",990              static_cast<int>(max_length - padding_length - ellipsis_length),991              c.GetCompletion().c_str());992      lines_printed++;993      continue;994    }995 996    // If we don't have a description, or we don't have enough space left to997    // print the separator followed by the ellipsis, we're done.998    if (c.GetDescription().empty() ||999        description_col + separator_length + ellipsis_length >= max_length) {1000      fprintf(output_file, "\n");1001      lines_printed++;1002      continue;1003    }1004 1005    // Print the separator.1006    fprintf(output_file, " -- ");1007 1008    // Descriptions can contain newlines. We want to print them below each1009    // other, aligned after the separator. For example, foo has a1010    // two-line description:1011    //1012    // foo   -- Something that fits on the line.1013    //          More information below.1014    //1015    // However, as soon as a line exceed the available screen width and1016    // print ellipsis, we don't print the next line. For example, foo has a1017    // three-line description:1018    //1019    // foo   -- Something that fits on the line.1020    //          Something much longer  that doesn't fit...1021    //1022    // Because we had to print ellipsis on line two, we don't print the1023    // third line.1024    bool first = true;1025    for (llvm::StringRef line : llvm::split(c.GetDescription(), '\n')) {1026      if (line.empty())1027        break;1028      if (max_height && lines_printed >= *max_height)1029        break;1030      if (!first)1031        fprintf(output_file, "%*s",1032                static_cast<int>(description_col + separator_length), "");1033 1034      first = false;1035      const size_t position = description_col + separator_length;1036      const size_t description_length = line.size();1037      if (position + description_length < max_length) {1038        fprintf(output_file, "%.*s\n", static_cast<int>(description_length),1039                line.data());1040        lines_printed++;1041      } else {1042        fprintf(output_file, "%.*s...\n",1043                static_cast<int>(max_length - position - ellipsis_length),1044                line.data());1045        lines_printed++;1046        continue;1047      }1048    }1049  }1050  return results_printed;1051}1052 1053void Editline::DisplayCompletions(1054    Editline &editline, llvm::ArrayRef<CompletionResult::Completion> results) {1055  assert(!results.empty());1056 1057  LockedStreamFile locked_stream = editline.m_output_stream_sp->Lock();1058 1059  fprintf(locked_stream.GetFile().GetStream(),1060          "\n" ANSI_CLEAR_BELOW "Available completions:\n");1061 1062  /// Account for the current line, the line showing "Available completions"1063  /// before and the line saying "More" after.1064  const size_t page_size = editline.GetTerminalHeight() - 3;1065 1066  bool all = false;1067 1068  auto longest =1069      std::max_element(results.begin(), results.end(), [](auto &c1, auto &c2) {1070        return c1.GetCompletion().size() < c2.GetCompletion().size();1071      });1072 1073  const size_t max_len = longest->GetCompletion().size();1074 1075  size_t cur_pos = 0;1076  while (cur_pos < results.size()) {1077    cur_pos += PrintCompletion(1078        locked_stream.GetFile().GetStream(), results.slice(cur_pos), max_len,1079        editline.GetTerminalWidth(),1080        all ? std::nullopt : std::optional<size_t>(page_size));1081 1082    if (cur_pos >= results.size())1083      break;1084 1085    fprintf(locked_stream.GetFile().GetStream(), "More (Y/n/a): ");1086    // The type for the output and the type for the parameter are different,1087    // to allow interoperability with older versions of libedit. The container1088    // for the reply must be as wide as what our implementation is using,1089    // but libedit may use a narrower type depending on the build1090    // configuration.1091    EditLineGetCharType reply = L'n';1092    int got_char = el_wgetc(editline.m_editline,1093                            reinterpret_cast<EditLineCharType *>(&reply));1094    // Check for a ^C or other interruption.1095    if (editline.m_editor_status == EditorStatus::Interrupted) {1096      editline.m_editor_status = EditorStatus::Editing;1097      fprintf(locked_stream.GetFile().GetStream(), "^C\n");1098      break;1099    }1100 1101    fprintf(locked_stream.GetFile().GetStream(), "\n");1102    if (got_char == -1 || reply == 'n')1103      break;1104    if (reply == 'a')1105      all = true;1106  }1107}1108 1109void Editline::UseColor(bool use_color) { m_color = use_color; }1110 1111unsigned char Editline::TabCommand(int ch) {1112  if (!m_completion_callback)1113    return CC_ERROR;1114 1115  const LineInfo *line_info = el_line(m_editline);1116 1117  llvm::StringRef line(line_info->buffer,1118                       line_info->lastchar - line_info->buffer);1119  unsigned cursor_index = line_info->cursor - line_info->buffer;1120  CompletionResult result;1121  CompletionRequest request(line, cursor_index, result);1122 1123  m_completion_callback(request);1124 1125  llvm::ArrayRef<CompletionResult::Completion> results = result.GetResults();1126 1127  StringList completions;1128  result.GetMatches(completions);1129 1130  if (results.size() == 0)1131    return CC_ERROR;1132 1133  if (results.size() == 1) {1134    CompletionResult::Completion completion = results.front();1135    switch (completion.GetMode()) {1136    case CompletionMode::Normal: {1137      std::string to_add = completion.GetCompletion();1138      // Terminate the current argument with a quote if it started with a quote.1139      Args &parsedLine = request.GetParsedLine();1140      if (!parsedLine.empty() && request.GetCursorIndex() < parsedLine.size() &&1141          request.GetParsedArg().IsQuoted()) {1142        to_add.push_back(request.GetParsedArg().GetQuoteChar());1143      }1144      to_add.push_back(' ');1145      el_deletestr(m_editline, request.GetCursorArgumentPrefix().size());1146      el_insertstr(m_editline, to_add.c_str());1147      // Clear all the autosuggestion parts if the only single space can be1148      // completed.1149      if (to_add == " ")1150        return CC_REDISPLAY;1151      return CC_REFRESH;1152    }1153    case CompletionMode::Partial: {1154      std::string to_add = completion.GetCompletion();1155      to_add = to_add.substr(request.GetCursorArgumentPrefix().size());1156      el_insertstr(m_editline, to_add.c_str());1157      break;1158    }1159    case CompletionMode::RewriteLine: {1160      el_deletestr(m_editline, line_info->cursor - line_info->buffer);1161      el_insertstr(m_editline, completion.GetCompletion().c_str());1162      break;1163    }1164    }1165    return CC_REDISPLAY;1166  }1167 1168  // If we get a longer match display that first.1169  std::string longest_prefix = completions.LongestCommonPrefix();1170  if (!longest_prefix.empty())1171    longest_prefix =1172        longest_prefix.substr(request.GetCursorArgumentPrefix().size());1173  if (!longest_prefix.empty()) {1174    el_insertstr(m_editline, longest_prefix.c_str());1175    return CC_REDISPLAY;1176  }1177 1178  DisplayCompletions(*this, results);1179 1180  DisplayInput();1181  MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);1182  return CC_REDISPLAY;1183}1184 1185unsigned char Editline::ApplyAutosuggestCommand(int ch) {1186  if (!m_suggestion_callback) {1187    return CC_REDISPLAY;1188  }1189 1190  const LineInfo *line_info = el_line(m_editline);1191  llvm::StringRef line(line_info->buffer,1192                       line_info->lastchar - line_info->buffer);1193 1194  if (std::optional<std::string> to_add = m_suggestion_callback(line))1195    el_insertstr(m_editline, to_add->c_str());1196 1197  return CC_REDISPLAY;1198}1199 1200unsigned char Editline::TypedCharacter(int ch) {1201  std::string typed = std::string(1, ch);1202  el_insertstr(m_editline, typed.c_str());1203 1204  if (!m_suggestion_callback) {1205    return CC_REDISPLAY;1206  }1207 1208  const LineInfo *line_info = el_line(m_editline);1209  llvm::StringRef line(line_info->buffer,1210                       line_info->lastchar - line_info->buffer);1211 1212  if (std::optional<std::string> to_add = m_suggestion_callback(line)) {1213    LockedStreamFile locked_stream = m_output_stream_sp->Lock();1214    std::string to_add_color =1215        m_suggestion_ansi_prefix + to_add.value() + m_suggestion_ansi_suffix;1216    fputs(typed.c_str(), locked_stream.GetFile().GetStream());1217    fputs(to_add_color.c_str(), locked_stream.GetFile().GetStream());1218    size_t new_autosuggestion_size = line.size() + to_add->length();1219    // Print spaces to hide any remains of a previous longer autosuggestion.1220    if (new_autosuggestion_size < m_previous_autosuggestion_size) {1221      size_t spaces_to_print =1222          m_previous_autosuggestion_size - new_autosuggestion_size;1223      std::string spaces = std::string(spaces_to_print, ' ');1224      fputs(spaces.c_str(), locked_stream.GetFile().GetStream());1225    }1226    m_previous_autosuggestion_size = new_autosuggestion_size;1227 1228    int editline_cursor_position =1229        (int)((line_info->cursor - line_info->buffer) + GetPromptWidth());1230    int editline_cursor_row = editline_cursor_position / m_terminal_width;1231    int toColumn =1232        editline_cursor_position - (editline_cursor_row * m_terminal_width);1233    fprintf(locked_stream.GetFile().GetStream(), ANSI_SET_COLUMN_N, toColumn);1234    return CC_REFRESH;1235  }1236 1237  return CC_REDISPLAY;1238}1239 1240void Editline::AddFunctionToEditLine(const EditLineCharType *command,1241                                     const EditLineCharType *helptext,1242                                     EditlineCommandCallbackType callbackFn) {1243  el_wset(m_editline, EL_ADDFN, command, helptext, callbackFn);1244}1245 1246void Editline::SetEditLinePromptCallback(1247    EditlinePromptCallbackType callbackFn) {1248  el_set(m_editline, EL_PROMPT, callbackFn);1249}1250 1251void Editline::SetGetCharacterFunction(EditlineGetCharCallbackType callbackFn) {1252  el_wset(m_editline, EL_GETCFN, callbackFn);1253}1254 1255void Editline::ConfigureEditor(bool multiline) {1256  if (m_editline && m_multiline_enabled == multiline)1257    return;1258  m_multiline_enabled = multiline;1259 1260  if (m_editline) {1261    // Disable edit mode to stop the terminal from flushing all input during1262    // the call to el_end() since we expect to have multiple editline instances1263    // in this program.1264    el_set(m_editline, EL_EDITMODE, 0);1265    el_end(m_editline);1266  }1267 1268  LockedStreamFile locked_output_stream = m_output_stream_sp->Lock();1269  LockedStreamFile locked_error_stream = m_output_stream_sp->Lock();1270  m_editline = el_init(m_editor_name.c_str(), m_input_file,1271                       locked_output_stream.GetFile().GetStream(),1272                       locked_error_stream.GetFile().GetStream());1273  ApplyTerminalSizeChange();1274 1275  if (m_history_sp && m_history_sp->IsValid()) {1276    if (!m_history_sp->Load()) {1277      fputs("Could not load history file\n.",1278            locked_output_stream.GetFile().GetStream());1279    }1280    el_wset(m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr());1281  }1282  el_set(m_editline, EL_CLIENTDATA, this);1283  el_set(m_editline, EL_SIGNAL, 0);1284  el_set(m_editline, EL_EDITOR, "emacs");1285 1286  SetGetCharacterFunction([](EditLine *editline, EditLineGetCharType *c) {1287    return Editline::InstanceFor(editline)->GetCharacter(c);1288  });1289 1290  SetEditLinePromptCallback([](EditLine *editline) {1291    return Editline::InstanceFor(editline)->Prompt();1292  });1293 1294  // Commands used for multiline support, registered whether or not they're1295  // used1296  AddFunctionToEditLine(1297      EditLineConstString("lldb-break-line"),1298      EditLineConstString("Insert a line break"),1299      [](EditLine *editline, int ch) {1300        return Editline::InstanceFor(editline)->BreakLineCommand(ch);1301      });1302 1303  AddFunctionToEditLine(1304      EditLineConstString("lldb-end-or-add-line"),1305      EditLineConstString("End editing or continue when incomplete"),1306      [](EditLine *editline, int ch) {1307        return Editline::InstanceFor(editline)->EndOrAddLineCommand(ch);1308      });1309  AddFunctionToEditLine(1310      EditLineConstString("lldb-delete-next-char"),1311      EditLineConstString("Delete next character"),1312      [](EditLine *editline, int ch) {1313        return Editline::InstanceFor(editline)->DeleteNextCharCommand(ch);1314      });1315  AddFunctionToEditLine(1316      EditLineConstString("lldb-delete-previous-char"),1317      EditLineConstString("Delete previous character"),1318      [](EditLine *editline, int ch) {1319        return Editline::InstanceFor(editline)->DeletePreviousCharCommand(ch);1320      });1321  AddFunctionToEditLine(1322      EditLineConstString("lldb-previous-line"),1323      EditLineConstString("Move to previous line"),1324      [](EditLine *editline, int ch) {1325        return Editline::InstanceFor(editline)->PreviousLineCommand(ch);1326      });1327  AddFunctionToEditLine(1328      EditLineConstString("lldb-next-line"),1329      EditLineConstString("Move to next line"), [](EditLine *editline, int ch) {1330        return Editline::InstanceFor(editline)->NextLineCommand(ch);1331      });1332  AddFunctionToEditLine(1333      EditLineConstString("lldb-previous-history"),1334      EditLineConstString("Move to previous history"),1335      [](EditLine *editline, int ch) {1336        return Editline::InstanceFor(editline)->PreviousHistoryCommand(ch);1337      });1338  AddFunctionToEditLine(1339      EditLineConstString("lldb-next-history"),1340      EditLineConstString("Move to next history"),1341      [](EditLine *editline, int ch) {1342        return Editline::InstanceFor(editline)->NextHistoryCommand(ch);1343      });1344  AddFunctionToEditLine(1345      EditLineConstString("lldb-buffer-start"),1346      EditLineConstString("Move to start of buffer"),1347      [](EditLine *editline, int ch) {1348        return Editline::InstanceFor(editline)->BufferStartCommand(ch);1349      });1350  AddFunctionToEditLine(1351      EditLineConstString("lldb-buffer-end"),1352      EditLineConstString("Move to end of buffer"),1353      [](EditLine *editline, int ch) {1354        return Editline::InstanceFor(editline)->BufferEndCommand(ch);1355      });1356  AddFunctionToEditLine(1357      EditLineConstString("lldb-fix-indentation"),1358      EditLineConstString("Fix line indentation"),1359      [](EditLine *editline, int ch) {1360        return Editline::InstanceFor(editline)->FixIndentationCommand(ch);1361      });1362 1363  // Register the complete callback under two names for compatibility with1364  // older clients using custom .editrc files (largely because libedit has a1365  // bad bug where if you have a bind command that tries to bind to a function1366  // name that doesn't exist, it can corrupt the heap and crash your process1367  // later.)1368  EditlineCommandCallbackType complete_callback = [](EditLine *editline,1369                                                     int ch) {1370    return Editline::InstanceFor(editline)->TabCommand(ch);1371  };1372  AddFunctionToEditLine(EditLineConstString("lldb-complete"),1373                        EditLineConstString("Invoke completion"),1374                        complete_callback);1375  AddFunctionToEditLine(EditLineConstString("lldb_complete"),1376                        EditLineConstString("Invoke completion"),1377                        complete_callback);1378 1379  // General bindings we don't mind being overridden1380  if (!multiline) {1381    el_set(m_editline, EL_BIND, "^r", "em-inc-search-prev",1382           NULL); // Cycle through backwards search, entering string1383 1384    if (m_suggestion_callback) {1385      AddFunctionToEditLine(1386          EditLineConstString("lldb-apply-complete"),1387          EditLineConstString("Adopt autocompletion"),1388          [](EditLine *editline, int ch) {1389            return Editline::InstanceFor(editline)->ApplyAutosuggestCommand(ch);1390          });1391 1392      el_set(m_editline, EL_BIND, "^f", "lldb-apply-complete",1393             NULL); // Apply a part that is suggested automatically1394 1395      AddFunctionToEditLine(1396          EditLineConstString("lldb-typed-character"),1397          EditLineConstString("Typed character"),1398          [](EditLine *editline, int ch) {1399            return Editline::InstanceFor(editline)->TypedCharacter(ch);1400          });1401 1402      char bind_key[2] = {0, 0};1403      llvm::StringRef ascii_chars =1404          "abcdefghijklmnopqrstuvwxzyABCDEFGHIJKLMNOPQRSTUVWXZY1234567890!\"#$%"1405          "&'()*+,./:;<=>?@[]_`{|}~ ";1406      for (char c : ascii_chars) {1407        bind_key[0] = c;1408        el_set(m_editline, EL_BIND, bind_key, "lldb-typed-character", NULL);1409      }1410      el_set(m_editline, EL_BIND, "\\-", "lldb-typed-character", NULL);1411      el_set(m_editline, EL_BIND, "\\^", "lldb-typed-character", NULL);1412      el_set(m_editline, EL_BIND, "\\\\", "lldb-typed-character", NULL);1413    }1414  }1415 1416  el_set(m_editline, EL_BIND, "^w", "ed-delete-prev-word",1417         NULL); // Delete previous word, behave like bash in emacs mode1418  el_set(m_editline, EL_BIND, "\t", "lldb-complete",1419         NULL); // Bind TAB to auto complete1420 1421  // Allow ctrl-left-arrow and ctrl-right-arrow for navigation, behave like1422  // bash in emacs mode.1423  el_set(m_editline, EL_BIND, ESCAPE "[1;5C", "em-next-word", NULL);1424  el_set(m_editline, EL_BIND, ESCAPE "[1;5D", "ed-prev-word", NULL);1425  el_set(m_editline, EL_BIND, ESCAPE "[5C", "em-next-word", NULL);1426  el_set(m_editline, EL_BIND, ESCAPE "[5D", "ed-prev-word", NULL);1427  el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[C", "em-next-word", NULL);1428  el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[D", "ed-prev-word", NULL);1429 1430  // Allow user-specific customization prior to registering bindings we1431  // absolutely require1432  el_source(m_editline, nullptr);1433 1434  // Register an internal binding that external developers shouldn't use1435  AddFunctionToEditLine(1436      EditLineConstString("lldb-revert-line"),1437      EditLineConstString("Revert line to saved state"),1438      [](EditLine *editline, int ch) {1439        return Editline::InstanceFor(editline)->RevertLineCommand(ch);1440      });1441 1442  // Register keys that perform auto-indent correction1443  if (m_fix_indentation_callback && m_fix_indentation_callback_chars) {1444    char bind_key[2] = {0, 0};1445    const char *indent_chars = m_fix_indentation_callback_chars;1446    while (*indent_chars) {1447      bind_key[0] = *indent_chars;1448      el_set(m_editline, EL_BIND, bind_key, "lldb-fix-indentation", NULL);1449      ++indent_chars;1450    }1451  }1452 1453  // Multi-line editor bindings1454  if (multiline) {1455    el_set(m_editline, EL_BIND, "\n", "lldb-end-or-add-line", NULL);1456    el_set(m_editline, EL_BIND, "\r", "lldb-end-or-add-line", NULL);1457    el_set(m_editline, EL_BIND, ESCAPE "\n", "lldb-break-line", NULL);1458    el_set(m_editline, EL_BIND, ESCAPE "\r", "lldb-break-line", NULL);1459    el_set(m_editline, EL_BIND, "^p", "lldb-previous-line", NULL);1460    el_set(m_editline, EL_BIND, "^n", "lldb-next-line", NULL);1461    el_set(m_editline, EL_BIND, "^?", "lldb-delete-previous-char", NULL);1462    el_set(m_editline, EL_BIND, "^d", "lldb-delete-next-char", NULL);1463    el_set(m_editline, EL_BIND, ESCAPE "[3~", "lldb-delete-next-char", NULL);1464    el_set(m_editline, EL_BIND, ESCAPE "[\\^", "lldb-revert-line", NULL);1465 1466    // Editor-specific bindings1467    if (IsEmacs()) {1468      el_set(m_editline, EL_BIND, ESCAPE "<", "lldb-buffer-start", NULL);1469      el_set(m_editline, EL_BIND, ESCAPE ">", "lldb-buffer-end", NULL);1470      el_set(m_editline, EL_BIND, ESCAPE "[A", "lldb-previous-line", NULL);1471      el_set(m_editline, EL_BIND, ESCAPE "[B", "lldb-next-line", NULL);1472      el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[A", "lldb-previous-history",1473             NULL);1474      el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[B", "lldb-next-history",1475             NULL);1476      el_set(m_editline, EL_BIND, ESCAPE "[1;3A", "lldb-previous-history",1477             NULL);1478      el_set(m_editline, EL_BIND, ESCAPE "[1;3B", "lldb-next-history", NULL);1479    } else {1480      el_set(m_editline, EL_BIND, "^H", "lldb-delete-previous-char", NULL);1481 1482      el_set(m_editline, EL_BIND, "-a", ESCAPE "[A", "lldb-previous-line",1483             NULL);1484      el_set(m_editline, EL_BIND, "-a", ESCAPE "[B", "lldb-next-line", NULL);1485      el_set(m_editline, EL_BIND, "-a", "x", "lldb-delete-next-char", NULL);1486      el_set(m_editline, EL_BIND, "-a", "^H", "lldb-delete-previous-char",1487             NULL);1488      el_set(m_editline, EL_BIND, "-a", "^?", "lldb-delete-previous-char",1489             NULL);1490 1491      // Escape is absorbed exiting edit mode, so re-register important1492      // sequences without the prefix1493      el_set(m_editline, EL_BIND, "-a", "[A", "lldb-previous-line", NULL);1494      el_set(m_editline, EL_BIND, "-a", "[B", "lldb-next-line", NULL);1495      el_set(m_editline, EL_BIND, "-a", "[\\^", "lldb-revert-line", NULL);1496    }1497  }1498}1499 1500// Editline public methods1501 1502Editline *Editline::InstanceFor(EditLine *editline) {1503  Editline *editor;1504  el_get(editline, EL_CLIENTDATA, &editor);1505  return editor;1506}1507 1508Editline::Editline(const char *editline_name, FILE *input_file,1509                   lldb::LockableStreamFileSP output_stream_sp,1510                   lldb::LockableStreamFileSP error_stream_sp, bool color)1511    : m_editor_status(EditorStatus::Complete), m_input_file(input_file),1512      m_output_stream_sp(output_stream_sp), m_error_stream_sp(error_stream_sp),1513      m_input_connection(fileno(input_file), false), m_color(color) {1514  assert(output_stream_sp && error_stream_sp);1515  // Get a shared history instance1516  m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name;1517  m_history_sp = EditlineHistory::GetHistory(m_editor_name);1518}1519 1520Editline::~Editline() {1521  if (m_editline) {1522    // Disable edit mode to stop the terminal from flushing all input during1523    // the call to el_end() since we expect to have multiple editline instances1524    // in this program.1525    el_set(m_editline, EL_EDITMODE, 0);1526    el_end(m_editline);1527    m_editline = nullptr;1528  }1529 1530  // EditlineHistory objects are sometimes shared between multiple Editline1531  // instances with the same program name. So just release our shared pointer1532  // and if we are the last owner, it will save the history to the history save1533  // file automatically.1534  m_history_sp.reset();1535}1536 1537void Editline::SetPrompt(const char *prompt) {1538  m_set_prompt = prompt == nullptr ? "" : prompt;1539}1540 1541void Editline::SetContinuationPrompt(const char *continuation_prompt) {1542  m_set_continuation_prompt =1543      continuation_prompt == nullptr ? "" : continuation_prompt;1544}1545 1546void Editline::TerminalSizeChanged() { m_terminal_size_has_changed = 1; }1547 1548void Editline::ApplyTerminalSizeChange() {1549  if (!m_editline)1550    return;1551 1552  m_terminal_size_has_changed = 0;1553  el_resize(m_editline);1554  int columns;1555  // This function is documenting as taking (const char *, void *) for the1556  // vararg part, but in reality in was consuming arguments until the first1557  // null pointer. This was fixed in libedit in April 20191558  // <http://mail-index.netbsd.org/source-changes/2019/04/26/msg105454.html>,1559  // but we're keeping the workaround until a version with that fix is more1560  // widely available.1561  if (el_get(m_editline, EL_GETTC, "co", &columns, nullptr) == 0) {1562    m_terminal_width = columns;1563    if (m_current_line_rows != -1) {1564      const LineInfoW *info = el_wline(m_editline);1565      int lineLength =1566          (int)((info->lastchar - info->buffer) + GetPromptWidth());1567      m_current_line_rows = (lineLength / columns) + 1;1568    }1569  } else {1570    m_terminal_width = INT_MAX;1571    m_current_line_rows = 1;1572  }1573 1574  int rows;1575  if (el_get(m_editline, EL_GETTC, "li", &rows, nullptr) == 0) {1576    m_terminal_height = rows;1577  } else {1578    m_terminal_height = INT_MAX;1579  }1580}1581 1582const char *Editline::GetPrompt() { return m_set_prompt.c_str(); }1583 1584uint32_t Editline::GetCurrentLine() { return m_current_line_index; }1585 1586bool Editline::Interrupt() {1587  bool result = true;1588  LockedStreamFile locked_stream = m_output_stream_sp->Lock();1589  if (m_editor_status == EditorStatus::Editing) {1590    fprintf(locked_stream.GetFile().GetStream(), "^C\n");1591    result = m_input_connection.InterruptRead();1592  }1593  m_editor_status = EditorStatus::Interrupted;1594  return result;1595}1596 1597bool Editline::Cancel() {1598  bool result = true;1599  LockedStreamFile locked_stream = m_output_stream_sp->Lock();1600  if (m_editor_status == EditorStatus::Editing) {1601    MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);1602    fprintf(locked_stream.GetFile().GetStream(), ANSI_CLEAR_BELOW);1603    result = m_input_connection.InterruptRead();1604  }1605  m_editor_status = EditorStatus::Interrupted;1606  return result;1607}1608 1609bool Editline::GetLine(std::string &line, bool &interrupted) {1610  ConfigureEditor(false);1611  m_input_lines = std::vector<EditLineStringType>();1612  m_input_lines.insert(m_input_lines.begin(), EditLineConstString(""));1613 1614  ScopedOptional<LockedStreamFile> scope(m_locked_output,1615                                         m_output_stream_sp->Lock());1616 1617  lldbassert(m_editor_status != EditorStatus::Editing);1618  if (m_editor_status == EditorStatus::Interrupted) {1619    m_editor_status = EditorStatus::Complete;1620    interrupted = true;1621    return true;1622  }1623 1624  SetCurrentLine(0);1625  m_in_history = false;1626  m_editor_status = EditorStatus::Editing;1627  m_revert_cursor_index = -1;1628 1629  lldbassert(m_output_stream_sp);1630  fprintf(m_locked_output->GetFile().GetStream(), "\r" ANSI_CLEAR_RIGHT);1631 1632  int count;1633  auto input = el_wgets(m_editline, &count);1634 1635  interrupted = m_editor_status == EditorStatus::Interrupted;1636  if (!interrupted) {1637    if (input == nullptr) {1638      fprintf(m_locked_output->GetFile().GetStream(), "\n");1639      m_editor_status = EditorStatus::EndOfInput;1640    } else {1641      m_history_sp->Enter(input);1642#if LLDB_EDITLINE_USE_WCHAR1643      llvm::convertWideToUTF8(SplitLines(input)[0], line);1644#else1645      line = SplitLines(input)[0];1646#endif1647      m_editor_status = EditorStatus::Complete;1648    }1649  }1650  return m_editor_status != EditorStatus::EndOfInput;1651}1652 1653bool Editline::GetLines(int first_line_number, StringList &lines,1654                        bool &interrupted) {1655  ConfigureEditor(true);1656 1657  // Print the initial input lines, then move the cursor back up to the start1658  // of input1659  SetBaseLineNumber(first_line_number);1660  m_input_lines = std::vector<EditLineStringType>();1661  m_input_lines.insert(m_input_lines.begin(), EditLineConstString(""));1662 1663  ScopedOptional<LockedStreamFile> scope(m_locked_output,1664                                         m_output_stream_sp->Lock());1665 1666  // Begin the line editing loop1667  DisplayInput();1668  SetCurrentLine(0);1669  MoveCursor(CursorLocation::BlockEnd, CursorLocation::BlockStart);1670  m_editor_status = EditorStatus::Editing;1671  m_in_history = false;1672 1673  m_revert_cursor_index = -1;1674  while (m_editor_status == EditorStatus::Editing) {1675    int count;1676    m_current_line_rows = -1;1677    el_wpush(m_editline, EditLineConstString(1678                             "\x1b[^")); // Revert to the existing line content1679    el_wgets(m_editline, &count);1680  }1681 1682  interrupted = m_editor_status == EditorStatus::Interrupted;1683  if (!interrupted) {1684    // Save the completed entry in history before returning. Don't save empty1685    // input as that just clutters the command history.1686    if (!m_input_lines.empty())1687      m_history_sp->Enter(CombineLines(m_input_lines).c_str());1688 1689    lines = GetInputAsStringList();1690  }1691  return m_editor_status != EditorStatus::EndOfInput;1692}1693 1694void Editline::PrintAsync(lldb::LockableStreamFileSP stream_sp, const char *s,1695                          size_t len) {1696  LockedStreamFile locked_stream = m_output_stream_sp->Lock();1697  if (m_editor_status == EditorStatus::Editing) {1698    SaveEditedLine();1699    MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);1700    fprintf(locked_stream.GetFile().GetStream(), ANSI_CLEAR_BELOW);1701  }1702  locked_stream.Write(s, len);1703  if (m_editor_status == EditorStatus::Editing) {1704    DisplayInput();1705    MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);1706  }1707}1708 1709void Editline::Refresh() {1710  if (!m_editline || !m_output_stream_sp)1711    return;1712  LockedStreamFile locked_stream = m_output_stream_sp->Lock();1713  el_set(m_editline, EL_REFRESH);1714}1715 1716bool Editline::CompleteCharacter(char ch, EditLineGetCharType &out) {1717#if !LLDB_EDITLINE_USE_WCHAR1718  if (ch == (char)EOF)1719    return false;1720 1721  out = (unsigned char)ch;1722  return true;1723#else1724  llvm::SmallString<4> input;1725  for (;;) {1726    input.push_back(ch);1727    auto *cur_ptr = reinterpret_cast<const llvm::UTF8 *>(input.begin());1728    auto *end_ptr = reinterpret_cast<const llvm::UTF8 *>(input.end());1729    llvm::UTF32 code_point = 0;1730    llvm::ConversionResult cr = llvm::convertUTF8Sequence(1731        &cur_ptr, end_ptr, &code_point, llvm::lenientConversion);1732    switch (cr) {1733    case llvm::conversionOK:1734      out = code_point;1735      return out != (EditLineGetCharType)WEOF;1736    case llvm::targetExhausted:1737    case llvm::sourceIllegal:1738      return false;1739    case llvm::sourceExhausted:1740      lldb::ConnectionStatus status;1741      size_t read_count = m_input_connection.Read(1742          &ch, 1, std::chrono::seconds(0), status, nullptr);1743      if (read_count == 0)1744        return false;1745      break;1746    }1747  }1748#endif1749}1750