Path: blob/main/contrib/llvm-project/lldb/source/Host/common/Editline.cpp
39607 views
//===-- Editline.cpp ------------------------------------------------------===//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 <climits>9#include <iomanip>10#include <optional>1112#include "lldb/Host/Editline.h"1314#include "lldb/Host/ConnectionFileDescriptor.h"15#include "lldb/Host/FileSystem.h"16#include "lldb/Host/Host.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"2526#include "llvm/Support/FileSystem.h"27#include "llvm/Support/Locale.h"28#include "llvm/Support/Threading.h"2930using namespace lldb_private;31using namespace lldb_private::line_editor;3233// Editline uses careful cursor management to achieve the illusion of editing a34// multi-line block of text with a single line editor. Preserving this35// illusion requires fairly careful management of cursor state. Read and36// understand the relationship between DisplayInput(), MoveCursor(),37// SetCurrentLine(), and SaveEditedLine() before making changes.3839/// https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf40#define ESCAPE "\x1b"41#define ANSI_CLEAR_BELOW ESCAPE "[J"42#define ANSI_CLEAR_RIGHT ESCAPE "[K"43#define ANSI_SET_COLUMN_N ESCAPE "[%dG"44#define ANSI_UP_N_ROWS ESCAPE "[%dA"45#define ANSI_DOWN_N_ROWS ESCAPE "[%dB"4647#if LLDB_EDITLINE_USE_WCHAR4849#define EditLineConstString(str) L##str50#define EditLineStringFormatSpec "%ls"5152#else5354#define EditLineConstString(str) str55#define EditLineStringFormatSpec "%s"5657// use #defines so wide version functions and structs will resolve to old58// versions for case of libedit not built with wide char support59#define history_w history60#define history_winit history_init61#define history_wend history_end62#define HistoryW History63#define HistEventW HistEvent64#define LineInfoW LineInfo6566#define el_wgets el_gets67#define el_wgetc el_getc68#define el_wpush el_push69#define el_wparse el_parse70#define el_wset el_set71#define el_wget el_get72#define el_wline el_line73#define el_winsertstr el_insertstr74#define el_wdeletestr el_deletestr7576#endif // #if LLDB_EDITLINE_USE_WCHAR7778bool IsOnlySpaces(const EditLineStringType &content) {79for (wchar_t ch : content) {80if (ch != EditLineCharType(' '))81return false;82}83return true;84}8586static size_t ColumnWidth(llvm::StringRef str) {87return llvm::sys::locale::columnWidth(str);88}8990static int GetOperation(HistoryOperation op) {91// The naming used by editline for the history operations is counter92// intuitive to how it's used in LLDB's editline implementation.93//94// - The H_LAST returns the oldest entry in the history.95//96// - The H_PREV operation returns the previous element in the history, which97// is newer than the current one.98//99// - The H_CURR returns the current entry in the history.100//101// - The H_NEXT operation returns the next element in the history, which is102// older than the current one.103//104// - The H_FIRST returns the most recent entry in the history.105//106// The naming of the enum entries match the semantic meaning.107switch(op) {108case HistoryOperation::Oldest:109return H_LAST;110case HistoryOperation::Older:111return H_NEXT;112case HistoryOperation::Current:113return H_CURR;114case HistoryOperation::Newer:115return H_PREV;116case HistoryOperation::Newest:117return H_FIRST;118}119llvm_unreachable("Fully covered switch!");120}121122123EditLineStringType CombineLines(const std::vector<EditLineStringType> &lines) {124EditLineStringStreamType combined_stream;125for (EditLineStringType line : lines) {126combined_stream << line.c_str() << "\n";127}128return combined_stream.str();129}130131std::vector<EditLineStringType> SplitLines(const EditLineStringType &input) {132std::vector<EditLineStringType> result;133size_t start = 0;134while (start < input.length()) {135size_t end = input.find('\n', start);136if (end == std::string::npos) {137result.push_back(input.substr(start));138break;139}140result.push_back(input.substr(start, end - start));141start = end + 1;142}143// Treat an empty history session as a single command of zero-length instead144// of returning an empty vector.145if (result.empty()) {146result.emplace_back();147}148return result;149}150151EditLineStringType FixIndentation(const EditLineStringType &line,152int indent_correction) {153if (indent_correction == 0)154return line;155if (indent_correction < 0)156return line.substr(-indent_correction);157return EditLineStringType(indent_correction, EditLineCharType(' ')) + line;158}159160int GetIndentation(const EditLineStringType &line) {161int space_count = 0;162for (EditLineCharType ch : line) {163if (ch != EditLineCharType(' '))164break;165++space_count;166}167return space_count;168}169170bool IsInputPending(FILE *file) {171// FIXME: This will be broken on Windows if we ever re-enable Editline. You172// can't use select173// on something that isn't a socket. This will have to be re-written to not174// use a FILE*, but instead use some kind of yet-to-be-created abstraction175// that select-like functionality on non-socket objects.176const int fd = fileno(file);177SelectHelper select_helper;178select_helper.SetTimeout(std::chrono::microseconds(0));179select_helper.FDSetRead(fd);180return select_helper.Select().Success();181}182183namespace lldb_private {184namespace line_editor {185typedef std::weak_ptr<EditlineHistory> EditlineHistoryWP;186187// EditlineHistory objects are sometimes shared between multiple Editline188// instances with the same program name.189190class EditlineHistory {191private:192// Use static GetHistory() function to get a EditlineHistorySP to one of193// these objects194EditlineHistory(const std::string &prefix, uint32_t size, bool unique_entries)195: m_prefix(prefix) {196m_history = history_winit();197history_w(m_history, &m_event, H_SETSIZE, size);198if (unique_entries)199history_w(m_history, &m_event, H_SETUNIQUE, 1);200}201202const char *GetHistoryFilePath() {203// Compute the history path lazily.204if (m_path.empty() && m_history && !m_prefix.empty()) {205llvm::SmallString<128> lldb_history_file;206FileSystem::Instance().GetHomeDirectory(lldb_history_file);207llvm::sys::path::append(lldb_history_file, ".lldb");208209// LLDB stores its history in ~/.lldb/. If for some reason this directory210// isn't writable or cannot be created, history won't be available.211if (!llvm::sys::fs::create_directory(lldb_history_file)) {212#if LLDB_EDITLINE_USE_WCHAR213std::string filename = m_prefix + "-widehistory";214#else215std::string filename = m_prefix + "-history";216#endif217llvm::sys::path::append(lldb_history_file, filename);218m_path = std::string(lldb_history_file.str());219}220}221222if (m_path.empty())223return nullptr;224225return m_path.c_str();226}227228public:229~EditlineHistory() {230Save();231232if (m_history) {233history_wend(m_history);234m_history = nullptr;235}236}237238static EditlineHistorySP GetHistory(const std::string &prefix) {239typedef std::map<std::string, EditlineHistoryWP> WeakHistoryMap;240static std::recursive_mutex g_mutex;241static WeakHistoryMap g_weak_map;242std::lock_guard<std::recursive_mutex> guard(g_mutex);243WeakHistoryMap::const_iterator pos = g_weak_map.find(prefix);244EditlineHistorySP history_sp;245if (pos != g_weak_map.end()) {246history_sp = pos->second.lock();247if (history_sp)248return history_sp;249g_weak_map.erase(pos);250}251history_sp.reset(new EditlineHistory(prefix, 800, true));252g_weak_map[prefix] = history_sp;253return history_sp;254}255256bool IsValid() const { return m_history != nullptr; }257258HistoryW *GetHistoryPtr() { return m_history; }259260void Enter(const EditLineCharType *line_cstr) {261if (m_history)262history_w(m_history, &m_event, H_ENTER, line_cstr);263}264265bool Load() {266if (m_history) {267const char *path = GetHistoryFilePath();268if (path) {269history_w(m_history, &m_event, H_LOAD, path);270return true;271}272}273return false;274}275276bool Save() {277if (m_history) {278const char *path = GetHistoryFilePath();279if (path) {280history_w(m_history, &m_event, H_SAVE, path);281return true;282}283}284return false;285}286287protected:288/// The history object.289HistoryW *m_history = nullptr;290/// The history event needed to contain all history events.291HistEventW m_event;292/// The prefix name (usually the editline program name) to use when293/// loading/saving history.294std::string m_prefix;295/// Path to the history file.296std::string m_path;297};298}299}300301// Editline private methods302303void Editline::SetBaseLineNumber(int line_number) {304m_base_line_number = line_number;305m_line_number_digits =306std::max<int>(3, std::to_string(line_number).length() + 1);307}308309std::string Editline::PromptForIndex(int line_index) {310bool use_line_numbers = m_multiline_enabled && m_base_line_number > 0;311std::string prompt = m_set_prompt;312if (use_line_numbers && prompt.length() == 0)313prompt = ": ";314std::string continuation_prompt = prompt;315if (m_set_continuation_prompt.length() > 0) {316continuation_prompt = m_set_continuation_prompt;317// Ensure that both prompts are the same length through space padding318const size_t prompt_width = ColumnWidth(prompt);319const size_t cont_prompt_width = ColumnWidth(continuation_prompt);320const size_t padded_prompt_width =321std::max(prompt_width, cont_prompt_width);322if (prompt_width < padded_prompt_width)323prompt += std::string(padded_prompt_width - prompt_width, ' ');324else if (cont_prompt_width < padded_prompt_width)325continuation_prompt +=326std::string(padded_prompt_width - cont_prompt_width, ' ');327}328329if (use_line_numbers) {330StreamString prompt_stream;331prompt_stream.Printf(332"%*d%s", m_line_number_digits, m_base_line_number + line_index,333(line_index == 0) ? prompt.c_str() : continuation_prompt.c_str());334return std::string(std::move(prompt_stream.GetString()));335}336return (line_index == 0) ? prompt : continuation_prompt;337}338339void Editline::SetCurrentLine(int line_index) {340m_current_line_index = line_index;341m_current_prompt = PromptForIndex(line_index);342}343344size_t Editline::GetPromptWidth() { return ColumnWidth(PromptForIndex(0)); }345346bool Editline::IsEmacs() {347const char *editor;348el_get(m_editline, EL_EDITOR, &editor);349return editor[0] == 'e';350}351352bool Editline::IsOnlySpaces() {353const LineInfoW *info = el_wline(m_editline);354for (const EditLineCharType *character = info->buffer;355character < info->lastchar; character++) {356if (*character != ' ')357return false;358}359return true;360}361362int Editline::GetLineIndexForLocation(CursorLocation location, int cursor_row) {363int line = 0;364if (location == CursorLocation::EditingPrompt ||365location == CursorLocation::BlockEnd ||366location == CursorLocation::EditingCursor) {367for (unsigned index = 0; index < m_current_line_index; index++) {368line += CountRowsForLine(m_input_lines[index]);369}370if (location == CursorLocation::EditingCursor) {371line += cursor_row;372} else if (location == CursorLocation::BlockEnd) {373for (unsigned index = m_current_line_index; index < m_input_lines.size();374index++) {375line += CountRowsForLine(m_input_lines[index]);376}377--line;378}379}380return line;381}382383void Editline::MoveCursor(CursorLocation from, CursorLocation to) {384const LineInfoW *info = el_wline(m_editline);385int editline_cursor_position =386(int)((info->cursor - info->buffer) + GetPromptWidth());387int editline_cursor_row = editline_cursor_position / m_terminal_width;388389// Determine relative starting and ending lines390int fromLine = GetLineIndexForLocation(from, editline_cursor_row);391int toLine = GetLineIndexForLocation(to, editline_cursor_row);392if (toLine != fromLine) {393fprintf(m_output_file,394(toLine > fromLine) ? ANSI_DOWN_N_ROWS : ANSI_UP_N_ROWS,395std::abs(toLine - fromLine));396}397398// Determine target column399int toColumn = 1;400if (to == CursorLocation::EditingCursor) {401toColumn =402editline_cursor_position - (editline_cursor_row * m_terminal_width) + 1;403} else if (to == CursorLocation::BlockEnd && !m_input_lines.empty()) {404toColumn =405((m_input_lines[m_input_lines.size() - 1].length() + GetPromptWidth()) %40680) +4071;408}409fprintf(m_output_file, ANSI_SET_COLUMN_N, toColumn);410}411412void Editline::DisplayInput(int firstIndex) {413fprintf(m_output_file, ANSI_SET_COLUMN_N ANSI_CLEAR_BELOW, 1);414int line_count = (int)m_input_lines.size();415for (int index = firstIndex; index < line_count; index++) {416fprintf(m_output_file,417"%s"418"%s"419"%s" EditLineStringFormatSpec " ",420m_prompt_ansi_prefix.c_str(), PromptForIndex(index).c_str(),421m_prompt_ansi_suffix.c_str(), m_input_lines[index].c_str());422if (index < line_count - 1)423fprintf(m_output_file, "\n");424}425}426427int Editline::CountRowsForLine(const EditLineStringType &content) {428std::string prompt =429PromptForIndex(0); // Prompt width is constant during an edit session430int line_length = (int)(content.length() + ColumnWidth(prompt));431return (line_length / m_terminal_width) + 1;432}433434void Editline::SaveEditedLine() {435const LineInfoW *info = el_wline(m_editline);436m_input_lines[m_current_line_index] =437EditLineStringType(info->buffer, info->lastchar - info->buffer);438}439440StringList Editline::GetInputAsStringList(int line_count) {441StringList lines;442for (EditLineStringType line : m_input_lines) {443if (line_count == 0)444break;445#if LLDB_EDITLINE_USE_WCHAR446lines.AppendString(m_utf8conv.to_bytes(line));447#else448lines.AppendString(line);449#endif450--line_count;451}452return lines;453}454455unsigned char Editline::RecallHistory(HistoryOperation op) {456assert(op == HistoryOperation::Older || op == HistoryOperation::Newer);457if (!m_history_sp || !m_history_sp->IsValid())458return CC_ERROR;459460HistoryW *pHistory = m_history_sp->GetHistoryPtr();461HistEventW history_event;462std::vector<EditLineStringType> new_input_lines;463464// Treat moving from the "live" entry differently465if (!m_in_history) {466switch (op) {467case HistoryOperation::Newer:468return CC_ERROR; // Can't go newer than the "live" entry469case HistoryOperation::Older: {470if (history_w(pHistory, &history_event,471GetOperation(HistoryOperation::Newest)) == -1)472return CC_ERROR;473// Save any edits to the "live" entry in case we return by moving forward474// in history (it would be more bash-like to save over any current entry,475// but libedit doesn't offer the ability to add entries anywhere except476// the end.)477SaveEditedLine();478m_live_history_lines = m_input_lines;479m_in_history = true;480} break;481default:482llvm_unreachable("unsupported history direction");483}484} else {485if (history_w(pHistory, &history_event, GetOperation(op)) == -1) {486switch (op) {487case HistoryOperation::Older:488// Can't move earlier than the earliest entry.489return CC_ERROR;490case HistoryOperation::Newer:491// Moving to newer-than-the-newest entry yields the "live" entry.492new_input_lines = m_live_history_lines;493m_in_history = false;494break;495default:496llvm_unreachable("unsupported history direction");497}498}499}500501// If we're pulling the lines from history, split them apart502if (m_in_history)503new_input_lines = SplitLines(history_event.str);504505// Erase the current edit session and replace it with a new one506MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);507m_input_lines = new_input_lines;508DisplayInput();509510// Prepare to edit the last line when moving to previous entry, or the first511// line when moving to next entry512switch (op) {513case HistoryOperation::Older:514m_current_line_index = (int)m_input_lines.size() - 1;515break;516case HistoryOperation::Newer:517m_current_line_index = 0;518break;519default:520llvm_unreachable("unsupported history direction");521}522SetCurrentLine(m_current_line_index);523MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt);524return CC_NEWLINE;525}526527int Editline::GetCharacter(EditLineGetCharType *c) {528const LineInfoW *info = el_wline(m_editline);529530// Paint a ANSI formatted version of the desired prompt over the version531// libedit draws. (will only be requested if colors are supported)532if (m_needs_prompt_repaint) {533MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);534fprintf(m_output_file,535"%s"536"%s"537"%s",538m_prompt_ansi_prefix.c_str(), Prompt(),539m_prompt_ansi_suffix.c_str());540MoveCursor(CursorLocation::EditingPrompt, CursorLocation::EditingCursor);541m_needs_prompt_repaint = false;542}543544if (m_multiline_enabled) {545// Detect when the number of rows used for this input line changes due to546// an edit547int lineLength = (int)((info->lastchar - info->buffer) + GetPromptWidth());548int new_line_rows = (lineLength / m_terminal_width) + 1;549if (m_current_line_rows != -1 && new_line_rows != m_current_line_rows) {550// Respond by repainting the current state from this line on551MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);552SaveEditedLine();553DisplayInput(m_current_line_index);554MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);555}556m_current_line_rows = new_line_rows;557}558559// Read an actual character560while (true) {561lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess;562char ch = 0;563564if (m_terminal_size_has_changed)565ApplyTerminalSizeChange();566567// This mutex is locked by our caller (GetLine). Unlock it while we read a568// character (blocking operation), so we do not hold the mutex569// indefinitely. This gives a chance for someone to interrupt us. After570// Read returns, immediately lock the mutex again and check if we were571// interrupted.572m_output_mutex.unlock();573int read_count =574m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr);575m_output_mutex.lock();576if (m_editor_status == EditorStatus::Interrupted) {577while (read_count > 0 && status == lldb::eConnectionStatusSuccess)578read_count =579m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr);580lldbassert(status == lldb::eConnectionStatusInterrupted);581return 0;582}583584if (read_count) {585if (CompleteCharacter(ch, *c))586return 1;587} else {588switch (status) {589case lldb::eConnectionStatusSuccess: // Success590break;591592case lldb::eConnectionStatusInterrupted:593llvm_unreachable("Interrupts should have been handled above.");594595case lldb::eConnectionStatusError: // Check GetError() for details596case lldb::eConnectionStatusTimedOut: // Request timed out597case lldb::eConnectionStatusEndOfFile: // End-of-file encountered598case lldb::eConnectionStatusNoConnection: // No connection599case lldb::eConnectionStatusLostConnection: // Lost connection while600// connected to a valid601// connection602m_editor_status = EditorStatus::EndOfInput;603return 0;604}605}606}607}608609const char *Editline::Prompt() {610if (!m_prompt_ansi_prefix.empty() || !m_prompt_ansi_suffix.empty())611m_needs_prompt_repaint = true;612return m_current_prompt.c_str();613}614615unsigned char Editline::BreakLineCommand(int ch) {616// Preserve any content beyond the cursor, truncate and save the current line617const LineInfoW *info = el_wline(m_editline);618auto current_line =619EditLineStringType(info->buffer, info->cursor - info->buffer);620auto new_line_fragment =621EditLineStringType(info->cursor, info->lastchar - info->cursor);622m_input_lines[m_current_line_index] = current_line;623624// Ignore whitespace-only extra fragments when breaking a line625if (::IsOnlySpaces(new_line_fragment))626new_line_fragment = EditLineConstString("");627628// Establish the new cursor position at the start of a line when inserting a629// line break630m_revert_cursor_index = 0;631632// Don't perform automatic formatting when pasting633if (!IsInputPending(m_input_file)) {634// Apply smart indentation635if (m_fix_indentation_callback) {636StringList lines = GetInputAsStringList(m_current_line_index + 1);637#if LLDB_EDITLINE_USE_WCHAR638lines.AppendString(m_utf8conv.to_bytes(new_line_fragment));639#else640lines.AppendString(new_line_fragment);641#endif642643int indent_correction = m_fix_indentation_callback(this, lines, 0);644new_line_fragment = FixIndentation(new_line_fragment, indent_correction);645m_revert_cursor_index = GetIndentation(new_line_fragment);646}647}648649// Insert the new line and repaint everything from the split line on down650m_input_lines.insert(m_input_lines.begin() + m_current_line_index + 1,651new_line_fragment);652MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);653DisplayInput(m_current_line_index);654655// Reposition the cursor to the right line and prepare to edit the new line656SetCurrentLine(m_current_line_index + 1);657MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt);658return CC_NEWLINE;659}660661unsigned char Editline::EndOrAddLineCommand(int ch) {662// Don't perform end of input detection when pasting, always treat this as a663// line break664if (IsInputPending(m_input_file)) {665return BreakLineCommand(ch);666}667668// Save any edits to this line669SaveEditedLine();670671// If this is the end of the last line, consider whether to add a line672// instead673const LineInfoW *info = el_wline(m_editline);674if (m_current_line_index == m_input_lines.size() - 1 &&675info->cursor == info->lastchar) {676if (m_is_input_complete_callback) {677auto lines = GetInputAsStringList();678if (!m_is_input_complete_callback(this, lines)) {679return BreakLineCommand(ch);680}681682// The completion test is allowed to change the input lines when complete683m_input_lines.clear();684for (unsigned index = 0; index < lines.GetSize(); index++) {685#if LLDB_EDITLINE_USE_WCHAR686m_input_lines.insert(m_input_lines.end(),687m_utf8conv.from_bytes(lines[index]));688#else689m_input_lines.insert(m_input_lines.end(), lines[index]);690#endif691}692}693}694MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockEnd);695fprintf(m_output_file, "\n");696m_editor_status = EditorStatus::Complete;697return CC_NEWLINE;698}699700unsigned char Editline::DeleteNextCharCommand(int ch) {701LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline));702703// Just delete the next character normally if possible704if (info->cursor < info->lastchar) {705info->cursor++;706el_deletestr(m_editline, 1);707return CC_REFRESH;708}709710// Fail when at the end of the last line, except when ^D is pressed on the711// line is empty, in which case it is treated as EOF712if (m_current_line_index == m_input_lines.size() - 1) {713if (ch == 4 && info->buffer == info->lastchar) {714fprintf(m_output_file, "^D\n");715m_editor_status = EditorStatus::EndOfInput;716return CC_EOF;717}718return CC_ERROR;719}720721// Prepare to combine this line with the one below722MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);723724// Insert the next line of text at the cursor and restore the cursor position725const EditLineCharType *cursor = info->cursor;726el_winsertstr(m_editline, m_input_lines[m_current_line_index + 1].c_str());727info->cursor = cursor;728SaveEditedLine();729730// Delete the extra line731m_input_lines.erase(m_input_lines.begin() + m_current_line_index + 1);732733// Clear and repaint from this line on down734DisplayInput(m_current_line_index);735MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);736return CC_REFRESH;737}738739unsigned char Editline::DeletePreviousCharCommand(int ch) {740LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline));741742// Just delete the previous character normally when not at the start of a743// line744if (info->cursor > info->buffer) {745el_deletestr(m_editline, 1);746return CC_REFRESH;747}748749// No prior line and no prior character? Let the user know750if (m_current_line_index == 0)751return CC_ERROR;752753// No prior character, but prior line? Combine with the line above754SaveEditedLine();755SetCurrentLine(m_current_line_index - 1);756auto priorLine = m_input_lines[m_current_line_index];757m_input_lines.erase(m_input_lines.begin() + m_current_line_index);758m_input_lines[m_current_line_index] =759priorLine + m_input_lines[m_current_line_index];760761// Repaint from the new line down762fprintf(m_output_file, ANSI_UP_N_ROWS ANSI_SET_COLUMN_N,763CountRowsForLine(priorLine), 1);764DisplayInput(m_current_line_index);765766// Put the cursor back where libedit expects it to be before returning to767// editing by telling libedit about the newly inserted text768MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt);769el_winsertstr(m_editline, priorLine.c_str());770return CC_REDISPLAY;771}772773unsigned char Editline::PreviousLineCommand(int ch) {774SaveEditedLine();775776if (m_current_line_index == 0) {777return RecallHistory(HistoryOperation::Older);778}779780// Start from a known location781MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);782783// Treat moving up from a blank last line as a deletion of that line784if (m_current_line_index == m_input_lines.size() - 1 && IsOnlySpaces()) {785m_input_lines.erase(m_input_lines.begin() + m_current_line_index);786fprintf(m_output_file, ANSI_CLEAR_BELOW);787}788789SetCurrentLine(m_current_line_index - 1);790fprintf(m_output_file, ANSI_UP_N_ROWS ANSI_SET_COLUMN_N,791CountRowsForLine(m_input_lines[m_current_line_index]), 1);792return CC_NEWLINE;793}794795unsigned char Editline::NextLineCommand(int ch) {796SaveEditedLine();797798// Handle attempts to move down from the last line799if (m_current_line_index == m_input_lines.size() - 1) {800// Don't add an extra line if the existing last line is blank, move through801// history instead802if (IsOnlySpaces()) {803return RecallHistory(HistoryOperation::Newer);804}805806// Determine indentation for the new line807int indentation = 0;808if (m_fix_indentation_callback) {809StringList lines = GetInputAsStringList();810lines.AppendString("");811indentation = m_fix_indentation_callback(this, lines, 0);812}813m_input_lines.insert(814m_input_lines.end(),815EditLineStringType(indentation, EditLineCharType(' ')));816}817818// Move down past the current line using newlines to force scrolling if819// needed820SetCurrentLine(m_current_line_index + 1);821const LineInfoW *info = el_wline(m_editline);822int cursor_position = (int)((info->cursor - info->buffer) + GetPromptWidth());823int cursor_row = cursor_position / m_terminal_width;824for (int line_count = 0; line_count < m_current_line_rows - cursor_row;825line_count++) {826fprintf(m_output_file, "\n");827}828return CC_NEWLINE;829}830831unsigned char Editline::PreviousHistoryCommand(int ch) {832SaveEditedLine();833834return RecallHistory(HistoryOperation::Older);835}836837unsigned char Editline::NextHistoryCommand(int ch) {838SaveEditedLine();839840return RecallHistory(HistoryOperation::Newer);841}842843unsigned char Editline::FixIndentationCommand(int ch) {844if (!m_fix_indentation_callback)845return CC_NORM;846847// Insert the character typed before proceeding848EditLineCharType inserted[] = {(EditLineCharType)ch, 0};849el_winsertstr(m_editline, inserted);850LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline));851int cursor_position = info->cursor - info->buffer;852853// Save the edits and determine the correct indentation level854SaveEditedLine();855StringList lines = GetInputAsStringList(m_current_line_index + 1);856int indent_correction =857m_fix_indentation_callback(this, lines, cursor_position);858859// If it is already correct no special work is needed860if (indent_correction == 0)861return CC_REFRESH;862863// Change the indentation level of the line864std::string currentLine = lines.GetStringAtIndex(m_current_line_index);865if (indent_correction > 0) {866currentLine = currentLine.insert(0, indent_correction, ' ');867} else {868currentLine = currentLine.erase(0, -indent_correction);869}870#if LLDB_EDITLINE_USE_WCHAR871m_input_lines[m_current_line_index] = m_utf8conv.from_bytes(currentLine);872#else873m_input_lines[m_current_line_index] = currentLine;874#endif875876// Update the display to reflect the change877MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);878DisplayInput(m_current_line_index);879880// Reposition the cursor back on the original line and prepare to restart881// editing with a new cursor position882SetCurrentLine(m_current_line_index);883MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt);884m_revert_cursor_index = cursor_position + indent_correction;885return CC_NEWLINE;886}887888unsigned char Editline::RevertLineCommand(int ch) {889el_winsertstr(m_editline, m_input_lines[m_current_line_index].c_str());890if (m_revert_cursor_index >= 0) {891LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline));892info->cursor = info->buffer + m_revert_cursor_index;893if (info->cursor > info->lastchar) {894info->cursor = info->lastchar;895}896m_revert_cursor_index = -1;897}898return CC_REFRESH;899}900901unsigned char Editline::BufferStartCommand(int ch) {902SaveEditedLine();903MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);904SetCurrentLine(0);905m_revert_cursor_index = 0;906return CC_NEWLINE;907}908909unsigned char Editline::BufferEndCommand(int ch) {910SaveEditedLine();911MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockEnd);912SetCurrentLine((int)m_input_lines.size() - 1);913MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt);914return CC_NEWLINE;915}916917/// Prints completions and their descriptions to the given file. Only the918/// completions in the interval [start, end) are printed.919static void920PrintCompletion(FILE *output_file,921llvm::ArrayRef<CompletionResult::Completion> results,922size_t max_len) {923for (const CompletionResult::Completion &c : results) {924fprintf(output_file, "\t%-*s", (int)max_len, c.GetCompletion().c_str());925if (!c.GetDescription().empty())926fprintf(output_file, " -- %s", c.GetDescription().c_str());927fprintf(output_file, "\n");928}929}930931void Editline::DisplayCompletions(932Editline &editline, llvm::ArrayRef<CompletionResult::Completion> results) {933assert(!results.empty());934935fprintf(editline.m_output_file,936"\n" ANSI_CLEAR_BELOW "Available completions:\n");937const size_t page_size = 40;938bool all = false;939940auto longest =941std::max_element(results.begin(), results.end(), [](auto &c1, auto &c2) {942return c1.GetCompletion().size() < c2.GetCompletion().size();943});944945const size_t max_len = longest->GetCompletion().size();946947if (results.size() < page_size) {948PrintCompletion(editline.m_output_file, results, max_len);949return;950}951952size_t cur_pos = 0;953while (cur_pos < results.size()) {954size_t remaining = results.size() - cur_pos;955size_t next_size = all ? remaining : std::min(page_size, remaining);956957PrintCompletion(editline.m_output_file, results.slice(cur_pos, next_size),958max_len);959960cur_pos += next_size;961962if (cur_pos >= results.size())963break;964965fprintf(editline.m_output_file, "More (Y/n/a): ");966// The type for the output and the type for the parameter are different,967// to allow interoperability with older versions of libedit. The container968// for the reply must be as wide as what our implementation is using,969// but libedit may use a narrower type depending on the build970// configuration.971EditLineGetCharType reply = L'n';972int got_char = el_wgetc(editline.m_editline,973reinterpret_cast<EditLineCharType *>(&reply));974// Check for a ^C or other interruption.975if (editline.m_editor_status == EditorStatus::Interrupted) {976editline.m_editor_status = EditorStatus::Editing;977fprintf(editline.m_output_file, "^C\n");978break;979}980981fprintf(editline.m_output_file, "\n");982if (got_char == -1 || reply == 'n')983break;984if (reply == 'a')985all = true;986}987}988989unsigned char Editline::TabCommand(int ch) {990if (!m_completion_callback)991return CC_ERROR;992993const LineInfo *line_info = el_line(m_editline);994995llvm::StringRef line(line_info->buffer,996line_info->lastchar - line_info->buffer);997unsigned cursor_index = line_info->cursor - line_info->buffer;998CompletionResult result;999CompletionRequest request(line, cursor_index, result);10001001m_completion_callback(request);10021003llvm::ArrayRef<CompletionResult::Completion> results = result.GetResults();10041005StringList completions;1006result.GetMatches(completions);10071008if (results.size() == 0)1009return CC_ERROR;10101011if (results.size() == 1) {1012CompletionResult::Completion completion = results.front();1013switch (completion.GetMode()) {1014case CompletionMode::Normal: {1015std::string to_add = completion.GetCompletion();1016// Terminate the current argument with a quote if it started with a quote.1017Args &parsedLine = request.GetParsedLine();1018if (!parsedLine.empty() && request.GetCursorIndex() < parsedLine.size() &&1019request.GetParsedArg().IsQuoted()) {1020to_add.push_back(request.GetParsedArg().GetQuoteChar());1021}1022to_add.push_back(' ');1023el_deletestr(m_editline, request.GetCursorArgumentPrefix().size());1024el_insertstr(m_editline, to_add.c_str());1025// Clear all the autosuggestion parts if the only single space can be completed.1026if (to_add == " ")1027return CC_REDISPLAY;1028return CC_REFRESH;1029}1030case CompletionMode::Partial: {1031std::string to_add = completion.GetCompletion();1032to_add = to_add.substr(request.GetCursorArgumentPrefix().size());1033el_insertstr(m_editline, to_add.c_str());1034break;1035}1036case CompletionMode::RewriteLine: {1037el_deletestr(m_editline, line_info->cursor - line_info->buffer);1038el_insertstr(m_editline, completion.GetCompletion().c_str());1039break;1040}1041}1042return CC_REDISPLAY;1043}10441045// If we get a longer match display that first.1046std::string longest_prefix = completions.LongestCommonPrefix();1047if (!longest_prefix.empty())1048longest_prefix =1049longest_prefix.substr(request.GetCursorArgumentPrefix().size());1050if (!longest_prefix.empty()) {1051el_insertstr(m_editline, longest_prefix.c_str());1052return CC_REDISPLAY;1053}10541055DisplayCompletions(*this, results);10561057DisplayInput();1058MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);1059return CC_REDISPLAY;1060}10611062unsigned char Editline::ApplyAutosuggestCommand(int ch) {1063if (!m_suggestion_callback) {1064return CC_REDISPLAY;1065}10661067const LineInfo *line_info = el_line(m_editline);1068llvm::StringRef line(line_info->buffer,1069line_info->lastchar - line_info->buffer);10701071if (std::optional<std::string> to_add = m_suggestion_callback(line))1072el_insertstr(m_editline, to_add->c_str());10731074return CC_REDISPLAY;1075}10761077unsigned char Editline::TypedCharacter(int ch) {1078std::string typed = std::string(1, ch);1079el_insertstr(m_editline, typed.c_str());10801081if (!m_suggestion_callback) {1082return CC_REDISPLAY;1083}10841085const LineInfo *line_info = el_line(m_editline);1086llvm::StringRef line(line_info->buffer,1087line_info->lastchar - line_info->buffer);10881089if (std::optional<std::string> to_add = m_suggestion_callback(line)) {1090std::string to_add_color =1091m_suggestion_ansi_prefix + to_add.value() + m_suggestion_ansi_suffix;1092fputs(typed.c_str(), m_output_file);1093fputs(to_add_color.c_str(), m_output_file);1094size_t new_autosuggestion_size = line.size() + to_add->length();1095// Print spaces to hide any remains of a previous longer autosuggestion.1096if (new_autosuggestion_size < m_previous_autosuggestion_size) {1097size_t spaces_to_print =1098m_previous_autosuggestion_size - new_autosuggestion_size;1099std::string spaces = std::string(spaces_to_print, ' ');1100fputs(spaces.c_str(), m_output_file);1101}1102m_previous_autosuggestion_size = new_autosuggestion_size;11031104int editline_cursor_position =1105(int)((line_info->cursor - line_info->buffer) + GetPromptWidth());1106int editline_cursor_row = editline_cursor_position / m_terminal_width;1107int toColumn =1108editline_cursor_position - (editline_cursor_row * m_terminal_width);1109fprintf(m_output_file, ANSI_SET_COLUMN_N, toColumn);1110return CC_REFRESH;1111}11121113return CC_REDISPLAY;1114}11151116void Editline::AddFunctionToEditLine(const EditLineCharType *command,1117const EditLineCharType *helptext,1118EditlineCommandCallbackType callbackFn) {1119el_wset(m_editline, EL_ADDFN, command, helptext, callbackFn);1120}11211122void Editline::SetEditLinePromptCallback(1123EditlinePromptCallbackType callbackFn) {1124el_set(m_editline, EL_PROMPT, callbackFn);1125}11261127void Editline::SetGetCharacterFunction(EditlineGetCharCallbackType callbackFn) {1128el_wset(m_editline, EL_GETCFN, callbackFn);1129}11301131void Editline::ConfigureEditor(bool multiline) {1132if (m_editline && m_multiline_enabled == multiline)1133return;1134m_multiline_enabled = multiline;11351136if (m_editline) {1137// Disable edit mode to stop the terminal from flushing all input during1138// the call to el_end() since we expect to have multiple editline instances1139// in this program.1140el_set(m_editline, EL_EDITMODE, 0);1141el_end(m_editline);1142}11431144m_editline =1145el_init(m_editor_name.c_str(), m_input_file, m_output_file, m_error_file);1146ApplyTerminalSizeChange();11471148if (m_history_sp && m_history_sp->IsValid()) {1149if (!m_history_sp->Load()) {1150fputs("Could not load history file\n.", m_output_file);1151}1152el_wset(m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr());1153}1154el_set(m_editline, EL_CLIENTDATA, this);1155el_set(m_editline, EL_SIGNAL, 0);1156el_set(m_editline, EL_EDITOR, "emacs");11571158SetGetCharacterFunction([](EditLine *editline, EditLineGetCharType *c) {1159return Editline::InstanceFor(editline)->GetCharacter(c);1160});11611162SetEditLinePromptCallback([](EditLine *editline) {1163return Editline::InstanceFor(editline)->Prompt();1164});11651166// Commands used for multiline support, registered whether or not they're1167// used1168AddFunctionToEditLine(1169EditLineConstString("lldb-break-line"),1170EditLineConstString("Insert a line break"),1171[](EditLine *editline, int ch) {1172return Editline::InstanceFor(editline)->BreakLineCommand(ch);1173});11741175AddFunctionToEditLine(1176EditLineConstString("lldb-end-or-add-line"),1177EditLineConstString("End editing or continue when incomplete"),1178[](EditLine *editline, int ch) {1179return Editline::InstanceFor(editline)->EndOrAddLineCommand(ch);1180});1181AddFunctionToEditLine(1182EditLineConstString("lldb-delete-next-char"),1183EditLineConstString("Delete next character"),1184[](EditLine *editline, int ch) {1185return Editline::InstanceFor(editline)->DeleteNextCharCommand(ch);1186});1187AddFunctionToEditLine(1188EditLineConstString("lldb-delete-previous-char"),1189EditLineConstString("Delete previous character"),1190[](EditLine *editline, int ch) {1191return Editline::InstanceFor(editline)->DeletePreviousCharCommand(ch);1192});1193AddFunctionToEditLine(1194EditLineConstString("lldb-previous-line"),1195EditLineConstString("Move to previous line"),1196[](EditLine *editline, int ch) {1197return Editline::InstanceFor(editline)->PreviousLineCommand(ch);1198});1199AddFunctionToEditLine(1200EditLineConstString("lldb-next-line"),1201EditLineConstString("Move to next line"), [](EditLine *editline, int ch) {1202return Editline::InstanceFor(editline)->NextLineCommand(ch);1203});1204AddFunctionToEditLine(1205EditLineConstString("lldb-previous-history"),1206EditLineConstString("Move to previous history"),1207[](EditLine *editline, int ch) {1208return Editline::InstanceFor(editline)->PreviousHistoryCommand(ch);1209});1210AddFunctionToEditLine(1211EditLineConstString("lldb-next-history"),1212EditLineConstString("Move to next history"),1213[](EditLine *editline, int ch) {1214return Editline::InstanceFor(editline)->NextHistoryCommand(ch);1215});1216AddFunctionToEditLine(1217EditLineConstString("lldb-buffer-start"),1218EditLineConstString("Move to start of buffer"),1219[](EditLine *editline, int ch) {1220return Editline::InstanceFor(editline)->BufferStartCommand(ch);1221});1222AddFunctionToEditLine(1223EditLineConstString("lldb-buffer-end"),1224EditLineConstString("Move to end of buffer"),1225[](EditLine *editline, int ch) {1226return Editline::InstanceFor(editline)->BufferEndCommand(ch);1227});1228AddFunctionToEditLine(1229EditLineConstString("lldb-fix-indentation"),1230EditLineConstString("Fix line indentation"),1231[](EditLine *editline, int ch) {1232return Editline::InstanceFor(editline)->FixIndentationCommand(ch);1233});12341235// Register the complete callback under two names for compatibility with1236// older clients using custom .editrc files (largely because libedit has a1237// bad bug where if you have a bind command that tries to bind to a function1238// name that doesn't exist, it can corrupt the heap and crash your process1239// later.)1240EditlineCommandCallbackType complete_callback = [](EditLine *editline,1241int ch) {1242return Editline::InstanceFor(editline)->TabCommand(ch);1243};1244AddFunctionToEditLine(EditLineConstString("lldb-complete"),1245EditLineConstString("Invoke completion"),1246complete_callback);1247AddFunctionToEditLine(EditLineConstString("lldb_complete"),1248EditLineConstString("Invoke completion"),1249complete_callback);12501251// General bindings we don't mind being overridden1252if (!multiline) {1253el_set(m_editline, EL_BIND, "^r", "em-inc-search-prev",1254NULL); // Cycle through backwards search, entering string12551256if (m_suggestion_callback) {1257AddFunctionToEditLine(1258EditLineConstString("lldb-apply-complete"),1259EditLineConstString("Adopt autocompletion"),1260[](EditLine *editline, int ch) {1261return Editline::InstanceFor(editline)->ApplyAutosuggestCommand(ch);1262});12631264el_set(m_editline, EL_BIND, "^f", "lldb-apply-complete",1265NULL); // Apply a part that is suggested automatically12661267AddFunctionToEditLine(1268EditLineConstString("lldb-typed-character"),1269EditLineConstString("Typed character"),1270[](EditLine *editline, int ch) {1271return Editline::InstanceFor(editline)->TypedCharacter(ch);1272});12731274char bind_key[2] = {0, 0};1275llvm::StringRef ascii_chars =1276"abcdefghijklmnopqrstuvwxzyABCDEFGHIJKLMNOPQRSTUVWXZY1234567890!\"#$%"1277"&'()*+,./:;<=>?@[]_`{|}~ ";1278for (char c : ascii_chars) {1279bind_key[0] = c;1280el_set(m_editline, EL_BIND, bind_key, "lldb-typed-character", NULL);1281}1282el_set(m_editline, EL_BIND, "\\-", "lldb-typed-character", NULL);1283el_set(m_editline, EL_BIND, "\\^", "lldb-typed-character", NULL);1284el_set(m_editline, EL_BIND, "\\\\", "lldb-typed-character", NULL);1285}1286}12871288el_set(m_editline, EL_BIND, "^w", "ed-delete-prev-word",1289NULL); // Delete previous word, behave like bash in emacs mode1290el_set(m_editline, EL_BIND, "\t", "lldb-complete",1291NULL); // Bind TAB to auto complete12921293// Allow ctrl-left-arrow and ctrl-right-arrow for navigation, behave like1294// bash in emacs mode.1295el_set(m_editline, EL_BIND, ESCAPE "[1;5C", "em-next-word", NULL);1296el_set(m_editline, EL_BIND, ESCAPE "[1;5D", "ed-prev-word", NULL);1297el_set(m_editline, EL_BIND, ESCAPE "[5C", "em-next-word", NULL);1298el_set(m_editline, EL_BIND, ESCAPE "[5D", "ed-prev-word", NULL);1299el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[C", "em-next-word", NULL);1300el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[D", "ed-prev-word", NULL);13011302// Allow user-specific customization prior to registering bindings we1303// absolutely require1304el_source(m_editline, nullptr);13051306// Register an internal binding that external developers shouldn't use1307AddFunctionToEditLine(1308EditLineConstString("lldb-revert-line"),1309EditLineConstString("Revert line to saved state"),1310[](EditLine *editline, int ch) {1311return Editline::InstanceFor(editline)->RevertLineCommand(ch);1312});13131314// Register keys that perform auto-indent correction1315if (m_fix_indentation_callback && m_fix_indentation_callback_chars) {1316char bind_key[2] = {0, 0};1317const char *indent_chars = m_fix_indentation_callback_chars;1318while (*indent_chars) {1319bind_key[0] = *indent_chars;1320el_set(m_editline, EL_BIND, bind_key, "lldb-fix-indentation", NULL);1321++indent_chars;1322}1323}13241325// Multi-line editor bindings1326if (multiline) {1327el_set(m_editline, EL_BIND, "\n", "lldb-end-or-add-line", NULL);1328el_set(m_editline, EL_BIND, "\r", "lldb-end-or-add-line", NULL);1329el_set(m_editline, EL_BIND, ESCAPE "\n", "lldb-break-line", NULL);1330el_set(m_editline, EL_BIND, ESCAPE "\r", "lldb-break-line", NULL);1331el_set(m_editline, EL_BIND, "^p", "lldb-previous-line", NULL);1332el_set(m_editline, EL_BIND, "^n", "lldb-next-line", NULL);1333el_set(m_editline, EL_BIND, "^?", "lldb-delete-previous-char", NULL);1334el_set(m_editline, EL_BIND, "^d", "lldb-delete-next-char", NULL);1335el_set(m_editline, EL_BIND, ESCAPE "[3~", "lldb-delete-next-char", NULL);1336el_set(m_editline, EL_BIND, ESCAPE "[\\^", "lldb-revert-line", NULL);13371338// Editor-specific bindings1339if (IsEmacs()) {1340el_set(m_editline, EL_BIND, ESCAPE "<", "lldb-buffer-start", NULL);1341el_set(m_editline, EL_BIND, ESCAPE ">", "lldb-buffer-end", NULL);1342el_set(m_editline, EL_BIND, ESCAPE "[A", "lldb-previous-line", NULL);1343el_set(m_editline, EL_BIND, ESCAPE "[B", "lldb-next-line", NULL);1344el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[A", "lldb-previous-history",1345NULL);1346el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[B", "lldb-next-history",1347NULL);1348el_set(m_editline, EL_BIND, ESCAPE "[1;3A", "lldb-previous-history",1349NULL);1350el_set(m_editline, EL_BIND, ESCAPE "[1;3B", "lldb-next-history", NULL);1351} else {1352el_set(m_editline, EL_BIND, "^H", "lldb-delete-previous-char", NULL);13531354el_set(m_editline, EL_BIND, "-a", ESCAPE "[A", "lldb-previous-line",1355NULL);1356el_set(m_editline, EL_BIND, "-a", ESCAPE "[B", "lldb-next-line", NULL);1357el_set(m_editline, EL_BIND, "-a", "x", "lldb-delete-next-char", NULL);1358el_set(m_editline, EL_BIND, "-a", "^H", "lldb-delete-previous-char",1359NULL);1360el_set(m_editline, EL_BIND, "-a", "^?", "lldb-delete-previous-char",1361NULL);13621363// Escape is absorbed exiting edit mode, so re-register important1364// sequences without the prefix1365el_set(m_editline, EL_BIND, "-a", "[A", "lldb-previous-line", NULL);1366el_set(m_editline, EL_BIND, "-a", "[B", "lldb-next-line", NULL);1367el_set(m_editline, EL_BIND, "-a", "[\\^", "lldb-revert-line", NULL);1368}1369}1370}13711372// Editline public methods13731374Editline *Editline::InstanceFor(EditLine *editline) {1375Editline *editor;1376el_get(editline, EL_CLIENTDATA, &editor);1377return editor;1378}13791380Editline::Editline(const char *editline_name, FILE *input_file,1381FILE *output_file, FILE *error_file,1382std::recursive_mutex &output_mutex)1383: m_editor_status(EditorStatus::Complete), m_input_file(input_file),1384m_output_file(output_file), m_error_file(error_file),1385m_input_connection(fileno(input_file), false),1386m_output_mutex(output_mutex) {1387// Get a shared history instance1388m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name;1389m_history_sp = EditlineHistory::GetHistory(m_editor_name);1390}13911392Editline::~Editline() {1393if (m_editline) {1394// Disable edit mode to stop the terminal from flushing all input during1395// the call to el_end() since we expect to have multiple editline instances1396// in this program.1397el_set(m_editline, EL_EDITMODE, 0);1398el_end(m_editline);1399m_editline = nullptr;1400}14011402// EditlineHistory objects are sometimes shared between multiple Editline1403// instances with the same program name. So just release our shared pointer1404// and if we are the last owner, it will save the history to the history save1405// file automatically.1406m_history_sp.reset();1407}14081409void Editline::SetPrompt(const char *prompt) {1410m_set_prompt = prompt == nullptr ? "" : prompt;1411}14121413void Editline::SetContinuationPrompt(const char *continuation_prompt) {1414m_set_continuation_prompt =1415continuation_prompt == nullptr ? "" : continuation_prompt;1416}14171418void Editline::TerminalSizeChanged() { m_terminal_size_has_changed = 1; }14191420void Editline::ApplyTerminalSizeChange() {1421if (!m_editline)1422return;14231424m_terminal_size_has_changed = 0;1425el_resize(m_editline);1426int columns;1427// This function is documenting as taking (const char *, void *) for the1428// vararg part, but in reality in was consuming arguments until the first1429// null pointer. This was fixed in libedit in April 20191430// <http://mail-index.netbsd.org/source-changes/2019/04/26/msg105454.html>,1431// but we're keeping the workaround until a version with that fix is more1432// widely available.1433if (el_get(m_editline, EL_GETTC, "co", &columns, nullptr) == 0) {1434m_terminal_width = columns;1435if (m_current_line_rows != -1) {1436const LineInfoW *info = el_wline(m_editline);1437int lineLength =1438(int)((info->lastchar - info->buffer) + GetPromptWidth());1439m_current_line_rows = (lineLength / columns) + 1;1440}1441} else {1442m_terminal_width = INT_MAX;1443m_current_line_rows = 1;1444}1445}14461447const char *Editline::GetPrompt() { return m_set_prompt.c_str(); }14481449uint32_t Editline::GetCurrentLine() { return m_current_line_index; }14501451bool Editline::Interrupt() {1452bool result = true;1453std::lock_guard<std::recursive_mutex> guard(m_output_mutex);1454if (m_editor_status == EditorStatus::Editing) {1455fprintf(m_output_file, "^C\n");1456result = m_input_connection.InterruptRead();1457}1458m_editor_status = EditorStatus::Interrupted;1459return result;1460}14611462bool Editline::Cancel() {1463bool result = true;1464std::lock_guard<std::recursive_mutex> guard(m_output_mutex);1465if (m_editor_status == EditorStatus::Editing) {1466MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);1467fprintf(m_output_file, ANSI_CLEAR_BELOW);1468result = m_input_connection.InterruptRead();1469}1470m_editor_status = EditorStatus::Interrupted;1471return result;1472}14731474bool Editline::GetLine(std::string &line, bool &interrupted) {1475ConfigureEditor(false);1476m_input_lines = std::vector<EditLineStringType>();1477m_input_lines.insert(m_input_lines.begin(), EditLineConstString(""));14781479std::lock_guard<std::recursive_mutex> guard(m_output_mutex);14801481lldbassert(m_editor_status != EditorStatus::Editing);1482if (m_editor_status == EditorStatus::Interrupted) {1483m_editor_status = EditorStatus::Complete;1484interrupted = true;1485return true;1486}14871488SetCurrentLine(0);1489m_in_history = false;1490m_editor_status = EditorStatus::Editing;1491m_revert_cursor_index = -1;14921493int count;1494auto input = el_wgets(m_editline, &count);14951496interrupted = m_editor_status == EditorStatus::Interrupted;1497if (!interrupted) {1498if (input == nullptr) {1499fprintf(m_output_file, "\n");1500m_editor_status = EditorStatus::EndOfInput;1501} else {1502m_history_sp->Enter(input);1503#if LLDB_EDITLINE_USE_WCHAR1504line = m_utf8conv.to_bytes(SplitLines(input)[0]);1505#else1506line = SplitLines(input)[0];1507#endif1508m_editor_status = EditorStatus::Complete;1509}1510}1511return m_editor_status != EditorStatus::EndOfInput;1512}15131514bool Editline::GetLines(int first_line_number, StringList &lines,1515bool &interrupted) {1516ConfigureEditor(true);15171518// Print the initial input lines, then move the cursor back up to the start1519// of input1520SetBaseLineNumber(first_line_number);1521m_input_lines = std::vector<EditLineStringType>();1522m_input_lines.insert(m_input_lines.begin(), EditLineConstString(""));15231524std::lock_guard<std::recursive_mutex> guard(m_output_mutex);1525// Begin the line editing loop1526DisplayInput();1527SetCurrentLine(0);1528MoveCursor(CursorLocation::BlockEnd, CursorLocation::BlockStart);1529m_editor_status = EditorStatus::Editing;1530m_in_history = false;15311532m_revert_cursor_index = -1;1533while (m_editor_status == EditorStatus::Editing) {1534int count;1535m_current_line_rows = -1;1536el_wpush(m_editline, EditLineConstString(1537"\x1b[^")); // Revert to the existing line content1538el_wgets(m_editline, &count);1539}15401541interrupted = m_editor_status == EditorStatus::Interrupted;1542if (!interrupted) {1543// Save the completed entry in history before returning. Don't save empty1544// input as that just clutters the command history.1545if (!m_input_lines.empty())1546m_history_sp->Enter(CombineLines(m_input_lines).c_str());15471548lines = GetInputAsStringList();1549}1550return m_editor_status != EditorStatus::EndOfInput;1551}15521553void Editline::PrintAsync(Stream *stream, const char *s, size_t len) {1554std::lock_guard<std::recursive_mutex> guard(m_output_mutex);1555if (m_editor_status == EditorStatus::Editing) {1556SaveEditedLine();1557MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);1558fprintf(m_output_file, ANSI_CLEAR_BELOW);1559}1560stream->Write(s, len);1561stream->Flush();1562if (m_editor_status == EditorStatus::Editing) {1563DisplayInput();1564MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);1565}1566}15671568bool Editline::CompleteCharacter(char ch, EditLineGetCharType &out) {1569#if !LLDB_EDITLINE_USE_WCHAR1570if (ch == (char)EOF)1571return false;15721573out = (unsigned char)ch;1574return true;1575#else1576std::codecvt_utf8<wchar_t> cvt;1577llvm::SmallString<4> input;1578for (;;) {1579const char *from_next;1580wchar_t *to_next;1581std::mbstate_t state = std::mbstate_t();1582input.push_back(ch);1583switch (cvt.in(state, input.begin(), input.end(), from_next, &out, &out + 1,1584to_next)) {1585case std::codecvt_base::ok:1586return out != (EditLineGetCharType)WEOF;15871588case std::codecvt_base::error:1589case std::codecvt_base::noconv:1590return false;15911592case std::codecvt_base::partial:1593lldb::ConnectionStatus status;1594size_t read_count = m_input_connection.Read(1595&ch, 1, std::chrono::seconds(0), status, nullptr);1596if (read_count == 0)1597return false;1598break;1599}1600}1601#endif1602}160316041605