diff options
author | 2020-08-03 14:33:06 +0000 | |
---|---|---|
committer | 2020-08-03 14:33:06 +0000 | |
commit | 061da546b983eb767bad15e67af1174fb0bcf31c (patch) | |
tree | 83c78b820819d70aa40c36d90447978b300078c5 /gnu/llvm/lldb/source/Host/common/Editline.cpp | |
parent | Import LLVM 10.0.0 release including clang, lld and lldb. (diff) | |
download | wireguard-openbsd-061da546b983eb767bad15e67af1174fb0bcf31c.tar.xz wireguard-openbsd-061da546b983eb767bad15e67af1174fb0bcf31c.zip |
Import LLVM 10.0.0 release including clang, lld and lldb.
ok hackroom
tested by plenty
Diffstat (limited to 'gnu/llvm/lldb/source/Host/common/Editline.cpp')
-rw-r--r-- | gnu/llvm/lldb/source/Host/common/Editline.cpp | 1503 |
1 files changed, 1503 insertions, 0 deletions
diff --git a/gnu/llvm/lldb/source/Host/common/Editline.cpp b/gnu/llvm/lldb/source/Host/common/Editline.cpp new file mode 100644 index 00000000000..5fd5a0cfc7f --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/Editline.cpp @@ -0,0 +1,1503 @@ +//===-- Editline.cpp --------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <iomanip> +#include <iostream> +#include <limits.h> + +#include "lldb/Host/ConnectionFileDescriptor.h" +#include "lldb/Host/Editline.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/Host.h" +#include "lldb/Utility/CompletionRequest.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/LLDBAssert.h" +#include "lldb/Utility/SelectHelper.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/StringList.h" +#include "lldb/Utility/Timeout.h" + +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Threading.h" + +using namespace lldb_private; +using namespace lldb_private::line_editor; + +// Workaround for what looks like an OS X-specific issue, but other platforms +// may benefit from something similar if issues arise. The libedit library +// doesn't explicitly initialize the curses termcap library, which it gets away +// with until TERM is set to VT100 where it stumbles over an implementation +// assumption that may not exist on other platforms. The setupterm() function +// would normally require headers that don't work gracefully in this context, +// so the function declaraction has been hoisted here. +#if defined(__APPLE__) +extern "C" { +int setupterm(char *term, int fildes, int *errret); +} +#define USE_SETUPTERM_WORKAROUND +#endif + +// Editline uses careful cursor management to achieve the illusion of editing a +// multi-line block of text with a single line editor. Preserving this +// illusion requires fairly careful management of cursor state. Read and +// understand the relationship between DisplayInput(), MoveCursor(), +// SetCurrentLine(), and SaveEditedLine() before making changes. + +#define ESCAPE "\x1b" +#define ANSI_FAINT ESCAPE "[2m" +#define ANSI_UNFAINT ESCAPE "[22m" +#define ANSI_CLEAR_BELOW ESCAPE "[J" +#define ANSI_CLEAR_RIGHT ESCAPE "[K" +#define ANSI_SET_COLUMN_N ESCAPE "[%dG" +#define ANSI_UP_N_ROWS ESCAPE "[%dA" +#define ANSI_DOWN_N_ROWS ESCAPE "[%dB" + +#if LLDB_EDITLINE_USE_WCHAR + +#define EditLineConstString(str) L##str +#define EditLineStringFormatSpec "%ls" + +#else + +#define EditLineConstString(str) str +#define EditLineStringFormatSpec "%s" + +// use #defines so wide version functions and structs will resolve to old +// versions for case of libedit not built with wide char support +#define history_w history +#define history_winit history_init +#define history_wend history_end +#define HistoryW History +#define HistEventW HistEvent +#define LineInfoW LineInfo + +#define el_wgets el_gets +#define el_wgetc el_getc +#define el_wpush el_push +#define el_wparse el_parse +#define el_wset el_set +#define el_wget el_get +#define el_wline el_line +#define el_winsertstr el_insertstr +#define el_wdeletestr el_deletestr + +#endif // #if LLDB_EDITLINE_USE_WCHAR + +bool IsOnlySpaces(const EditLineStringType &content) { + for (wchar_t ch : content) { + if (ch != EditLineCharType(' ')) + return false; + } + return true; +} + +static int GetOperation(HistoryOperation op) { + // The naming used by editline for the history operations is counter + // intuitive to how it's used here. + // + // - The H_PREV operation returns the previous element in the history, which + // is newer than the current one. + // + // - The H_NEXT operation returns the next element in the history, which is + // older than the current one. + // + // The naming of the enum entries match the semantic meaning. + switch(op) { + case HistoryOperation::Oldest: + return H_FIRST; + case HistoryOperation::Older: + return H_NEXT; + case HistoryOperation::Current: + return H_CURR; + case HistoryOperation::Newer: + return H_PREV; + case HistoryOperation::Newest: + return H_LAST; + } + llvm_unreachable("Fully covered switch!"); +} + + +EditLineStringType CombineLines(const std::vector<EditLineStringType> &lines) { + EditLineStringStreamType combined_stream; + for (EditLineStringType line : lines) { + combined_stream << line.c_str() << "\n"; + } + return combined_stream.str(); +} + +std::vector<EditLineStringType> SplitLines(const EditLineStringType &input) { + std::vector<EditLineStringType> result; + size_t start = 0; + while (start < input.length()) { + size_t end = input.find('\n', start); + if (end == std::string::npos) { + result.insert(result.end(), input.substr(start)); + break; + } + result.insert(result.end(), input.substr(start, end - start)); + start = end + 1; + } + return result; +} + +EditLineStringType FixIndentation(const EditLineStringType &line, + int indent_correction) { + if (indent_correction == 0) + return line; + if (indent_correction < 0) + return line.substr(-indent_correction); + return EditLineStringType(indent_correction, EditLineCharType(' ')) + line; +} + +int GetIndentation(const EditLineStringType &line) { + int space_count = 0; + for (EditLineCharType ch : line) { + if (ch != EditLineCharType(' ')) + break; + ++space_count; + } + return space_count; +} + +bool IsInputPending(FILE *file) { + // FIXME: This will be broken on Windows if we ever re-enable Editline. You + // can't use select + // on something that isn't a socket. This will have to be re-written to not + // use a FILE*, but instead use some kind of yet-to-be-created abstraction + // that select-like functionality on non-socket objects. + const int fd = fileno(file); + SelectHelper select_helper; + select_helper.SetTimeout(std::chrono::microseconds(0)); + select_helper.FDSetRead(fd); + return select_helper.Select().Success(); +} + +namespace lldb_private { +namespace line_editor { +typedef std::weak_ptr<EditlineHistory> EditlineHistoryWP; + +// EditlineHistory objects are sometimes shared between multiple Editline +// instances with the same program name. + +class EditlineHistory { +private: + // Use static GetHistory() function to get a EditlineHistorySP to one of + // these objects + EditlineHistory(const std::string &prefix, uint32_t size, bool unique_entries) + : m_history(nullptr), m_event(), m_prefix(prefix), m_path() { + m_history = history_winit(); + history_w(m_history, &m_event, H_SETSIZE, size); + if (unique_entries) + history_w(m_history, &m_event, H_SETUNIQUE, 1); + } + + const char *GetHistoryFilePath() { + // Compute the history path lazily. + if (m_path.empty() && m_history && !m_prefix.empty()) { + llvm::SmallString<128> lldb_history_file; + llvm::sys::path::home_directory(lldb_history_file); + llvm::sys::path::append(lldb_history_file, ".lldb"); + + // LLDB stores its history in ~/.lldb/. If for some reason this directory + // isn't writable or cannot be created, history won't be available. + if (!llvm::sys::fs::create_directory(lldb_history_file)) { +#if LLDB_EDITLINE_USE_WCHAR + std::string filename = m_prefix + "-widehistory"; +#else + std::string filename = m_prefix + "-history"; +#endif + llvm::sys::path::append(lldb_history_file, filename); + m_path = lldb_history_file.str(); + } + } + + if (m_path.empty()) + return nullptr; + + return m_path.c_str(); + } + +public: + ~EditlineHistory() { + Save(); + + if (m_history) { + history_wend(m_history); + m_history = nullptr; + } + } + + static EditlineHistorySP GetHistory(const std::string &prefix) { + typedef std::map<std::string, EditlineHistoryWP> WeakHistoryMap; + static std::recursive_mutex g_mutex; + static WeakHistoryMap g_weak_map; + std::lock_guard<std::recursive_mutex> guard(g_mutex); + WeakHistoryMap::const_iterator pos = g_weak_map.find(prefix); + EditlineHistorySP history_sp; + if (pos != g_weak_map.end()) { + history_sp = pos->second.lock(); + if (history_sp) + return history_sp; + g_weak_map.erase(pos); + } + history_sp.reset(new EditlineHistory(prefix, 800, true)); + g_weak_map[prefix] = history_sp; + return history_sp; + } + + bool IsValid() const { return m_history != nullptr; } + + HistoryW *GetHistoryPtr() { return m_history; } + + void Enter(const EditLineCharType *line_cstr) { + if (m_history) + history_w(m_history, &m_event, H_ENTER, line_cstr); + } + + bool Load() { + if (m_history) { + const char *path = GetHistoryFilePath(); + if (path) { + history_w(m_history, &m_event, H_LOAD, path); + return true; + } + } + return false; + } + + bool Save() { + if (m_history) { + const char *path = GetHistoryFilePath(); + if (path) { + history_w(m_history, &m_event, H_SAVE, path); + return true; + } + } + return false; + } + +protected: + HistoryW *m_history; // The history object + HistEventW m_event; // The history event needed to contain all history events + std::string m_prefix; // The prefix name (usually the editline program name) + // to use when loading/saving history + std::string m_path; // Path to the history file +}; +} +} + +// Editline private methods + +void Editline::SetBaseLineNumber(int line_number) { + std::stringstream line_number_stream; + line_number_stream << line_number; + m_base_line_number = line_number; + m_line_number_digits = + std::max(3, (int)line_number_stream.str().length() + 1); +} + +std::string Editline::PromptForIndex(int line_index) { + bool use_line_numbers = m_multiline_enabled && m_base_line_number > 0; + std::string prompt = m_set_prompt; + if (use_line_numbers && prompt.length() == 0) { + prompt = ": "; + } + std::string continuation_prompt = prompt; + if (m_set_continuation_prompt.length() > 0) { + continuation_prompt = m_set_continuation_prompt; + + // Ensure that both prompts are the same length through space padding + while (continuation_prompt.length() < prompt.length()) { + continuation_prompt += ' '; + } + while (prompt.length() < continuation_prompt.length()) { + prompt += ' '; + } + } + + if (use_line_numbers) { + StreamString prompt_stream; + prompt_stream.Printf( + "%*d%s", m_line_number_digits, m_base_line_number + line_index, + (line_index == 0) ? prompt.c_str() : continuation_prompt.c_str()); + return std::move(prompt_stream.GetString()); + } + return (line_index == 0) ? prompt : continuation_prompt; +} + +void Editline::SetCurrentLine(int line_index) { + m_current_line_index = line_index; + m_current_prompt = PromptForIndex(line_index); +} + +int Editline::GetPromptWidth() { return (int)PromptForIndex(0).length(); } + +bool Editline::IsEmacs() { + const char *editor; + el_get(m_editline, EL_EDITOR, &editor); + return editor[0] == 'e'; +} + +bool Editline::IsOnlySpaces() { + const LineInfoW *info = el_wline(m_editline); + for (const EditLineCharType *character = info->buffer; + character < info->lastchar; character++) { + if (*character != ' ') + return false; + } + return true; +} + +int Editline::GetLineIndexForLocation(CursorLocation location, int cursor_row) { + int line = 0; + if (location == CursorLocation::EditingPrompt || + location == CursorLocation::BlockEnd || + location == CursorLocation::EditingCursor) { + for (unsigned index = 0; index < m_current_line_index; index++) { + line += CountRowsForLine(m_input_lines[index]); + } + if (location == CursorLocation::EditingCursor) { + line += cursor_row; + } else if (location == CursorLocation::BlockEnd) { + for (unsigned index = m_current_line_index; index < m_input_lines.size(); + index++) { + line += CountRowsForLine(m_input_lines[index]); + } + --line; + } + } + return line; +} + +void Editline::MoveCursor(CursorLocation from, CursorLocation to) { + const LineInfoW *info = el_wline(m_editline); + int editline_cursor_position = + (int)((info->cursor - info->buffer) + GetPromptWidth()); + int editline_cursor_row = editline_cursor_position / m_terminal_width; + + // Determine relative starting and ending lines + int fromLine = GetLineIndexForLocation(from, editline_cursor_row); + int toLine = GetLineIndexForLocation(to, editline_cursor_row); + if (toLine != fromLine) { + fprintf(m_output_file, + (toLine > fromLine) ? ANSI_DOWN_N_ROWS : ANSI_UP_N_ROWS, + std::abs(toLine - fromLine)); + } + + // Determine target column + int toColumn = 1; + if (to == CursorLocation::EditingCursor) { + toColumn = + editline_cursor_position - (editline_cursor_row * m_terminal_width) + 1; + } else if (to == CursorLocation::BlockEnd && !m_input_lines.empty()) { + toColumn = + ((m_input_lines[m_input_lines.size() - 1].length() + GetPromptWidth()) % + 80) + + 1; + } + fprintf(m_output_file, ANSI_SET_COLUMN_N, toColumn); +} + +void Editline::DisplayInput(int firstIndex) { + fprintf(m_output_file, ANSI_SET_COLUMN_N ANSI_CLEAR_BELOW, 1); + int line_count = (int)m_input_lines.size(); + const char *faint = m_color_prompts ? ANSI_FAINT : ""; + const char *unfaint = m_color_prompts ? ANSI_UNFAINT : ""; + + for (int index = firstIndex; index < line_count; index++) { + fprintf(m_output_file, "%s" + "%s" + "%s" EditLineStringFormatSpec " ", + faint, PromptForIndex(index).c_str(), unfaint, + m_input_lines[index].c_str()); + if (index < line_count - 1) + fprintf(m_output_file, "\n"); + } +} + +int Editline::CountRowsForLine(const EditLineStringType &content) { + auto prompt = + PromptForIndex(0); // Prompt width is constant during an edit session + int line_length = (int)(content.length() + prompt.length()); + return (line_length / m_terminal_width) + 1; +} + +void Editline::SaveEditedLine() { + const LineInfoW *info = el_wline(m_editline); + m_input_lines[m_current_line_index] = + EditLineStringType(info->buffer, info->lastchar - info->buffer); +} + +StringList Editline::GetInputAsStringList(int line_count) { + StringList lines; + for (EditLineStringType line : m_input_lines) { + if (line_count == 0) + break; +#if LLDB_EDITLINE_USE_WCHAR + lines.AppendString(m_utf8conv.to_bytes(line)); +#else + lines.AppendString(line); +#endif + --line_count; + } + return lines; +} + +unsigned char Editline::RecallHistory(HistoryOperation op) { + assert(op == HistoryOperation::Older || op == HistoryOperation::Newer); + if (!m_history_sp || !m_history_sp->IsValid()) + return CC_ERROR; + + HistoryW *pHistory = m_history_sp->GetHistoryPtr(); + HistEventW history_event; + std::vector<EditLineStringType> new_input_lines; + + // Treat moving from the "live" entry differently + if (!m_in_history) { + switch (op) { + case HistoryOperation::Newer: + return CC_ERROR; // Can't go newer than the "live" entry + case HistoryOperation::Older: { + if (history_w(pHistory, &history_event, + GetOperation(HistoryOperation::Newest)) == -1) + return CC_ERROR; + // Save any edits to the "live" entry in case we return by moving forward + // in history (it would be more bash-like to save over any current entry, + // but libedit doesn't offer the ability to add entries anywhere except + // the end.) + SaveEditedLine(); + m_live_history_lines = m_input_lines; + m_in_history = true; + } break; + default: + llvm_unreachable("unsupported history direction"); + } + } else { + if (history_w(pHistory, &history_event, GetOperation(op)) == -1) { + switch (op) { + case HistoryOperation::Older: + // Can't move earlier than the earliest entry. + return CC_ERROR; + case HistoryOperation::Newer: + // Moving to newer-than-the-newest entry yields the "live" entry. + new_input_lines = m_live_history_lines; + m_in_history = false; + break; + default: + llvm_unreachable("unsupported history direction"); + } + } + } + + // If we're pulling the lines from history, split them apart + if (m_in_history) + new_input_lines = SplitLines(history_event.str); + + // Erase the current edit session and replace it with a new one + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart); + m_input_lines = new_input_lines; + DisplayInput(); + + // Prepare to edit the last line when moving to previous entry, or the first + // line when moving to next entry + switch (op) { + case HistoryOperation::Older: + m_current_line_index = (int)m_input_lines.size() - 1; + break; + case HistoryOperation::Newer: + m_current_line_index = 0; + break; + default: + llvm_unreachable("unsupported history direction"); + } + SetCurrentLine(m_current_line_index); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + return CC_NEWLINE; +} + +int Editline::GetCharacter(EditLineGetCharType *c) { + const LineInfoW *info = el_wline(m_editline); + + // Paint a faint version of the desired prompt over the version libedit draws + // (will only be requested if colors are supported) + if (m_needs_prompt_repaint) { + MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + fprintf(m_output_file, "%s" + "%s" + "%s", + ANSI_FAINT, Prompt(), ANSI_UNFAINT); + MoveCursor(CursorLocation::EditingPrompt, CursorLocation::EditingCursor); + m_needs_prompt_repaint = false; + } + + if (m_multiline_enabled) { + // Detect when the number of rows used for this input line changes due to + // an edit + int lineLength = (int)((info->lastchar - info->buffer) + GetPromptWidth()); + int new_line_rows = (lineLength / m_terminal_width) + 1; + if (m_current_line_rows != -1 && new_line_rows != m_current_line_rows) { + // Respond by repainting the current state from this line on + MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + SaveEditedLine(); + DisplayInput(m_current_line_index); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor); + } + m_current_line_rows = new_line_rows; + } + + // Read an actual character + while (true) { + lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess; + char ch = 0; + + // This mutex is locked by our caller (GetLine). Unlock it while we read a + // character (blocking operation), so we do not hold the mutex + // indefinitely. This gives a chance for someone to interrupt us. After + // Read returns, immediately lock the mutex again and check if we were + // interrupted. + m_output_mutex.unlock(); + int read_count = + m_input_connection.Read(&ch, 1, llvm::None, status, nullptr); + m_output_mutex.lock(); + if (m_editor_status == EditorStatus::Interrupted) { + while (read_count > 0 && status == lldb::eConnectionStatusSuccess) + read_count = + m_input_connection.Read(&ch, 1, llvm::None, status, nullptr); + lldbassert(status == lldb::eConnectionStatusInterrupted); + return 0; + } + + if (read_count) { + if (CompleteCharacter(ch, *c)) + return 1; + } else { + switch (status) { + case lldb::eConnectionStatusSuccess: // Success + break; + + case lldb::eConnectionStatusInterrupted: + llvm_unreachable("Interrupts should have been handled above."); + + case lldb::eConnectionStatusError: // Check GetError() for details + case lldb::eConnectionStatusTimedOut: // Request timed out + case lldb::eConnectionStatusEndOfFile: // End-of-file encountered + case lldb::eConnectionStatusNoConnection: // No connection + case lldb::eConnectionStatusLostConnection: // Lost connection while + // connected to a valid + // connection + m_editor_status = EditorStatus::EndOfInput; + return 0; + } + } + } +} + +const char *Editline::Prompt() { + if (m_color_prompts) + m_needs_prompt_repaint = true; + return m_current_prompt.c_str(); +} + +unsigned char Editline::BreakLineCommand(int ch) { + // Preserve any content beyond the cursor, truncate and save the current line + const LineInfoW *info = el_wline(m_editline); + auto current_line = + EditLineStringType(info->buffer, info->cursor - info->buffer); + auto new_line_fragment = + EditLineStringType(info->cursor, info->lastchar - info->cursor); + m_input_lines[m_current_line_index] = current_line; + + // Ignore whitespace-only extra fragments when breaking a line + if (::IsOnlySpaces(new_line_fragment)) + new_line_fragment = EditLineConstString(""); + + // Establish the new cursor position at the start of a line when inserting a + // line break + m_revert_cursor_index = 0; + + // Don't perform automatic formatting when pasting + if (!IsInputPending(m_input_file)) { + // Apply smart indentation + if (m_fix_indentation_callback) { + StringList lines = GetInputAsStringList(m_current_line_index + 1); +#if LLDB_EDITLINE_USE_WCHAR + lines.AppendString(m_utf8conv.to_bytes(new_line_fragment)); +#else + lines.AppendString(new_line_fragment); +#endif + + int indent_correction = m_fix_indentation_callback( + this, lines, 0, m_fix_indentation_callback_baton); + new_line_fragment = FixIndentation(new_line_fragment, indent_correction); + m_revert_cursor_index = GetIndentation(new_line_fragment); + } + } + + // Insert the new line and repaint everything from the split line on down + m_input_lines.insert(m_input_lines.begin() + m_current_line_index + 1, + new_line_fragment); + MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + DisplayInput(m_current_line_index); + + // Reposition the cursor to the right line and prepare to edit the new line + SetCurrentLine(m_current_line_index + 1); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + return CC_NEWLINE; +} + +unsigned char Editline::EndOrAddLineCommand(int ch) { + // Don't perform end of input detection when pasting, always treat this as a + // line break + if (IsInputPending(m_input_file)) { + return BreakLineCommand(ch); + } + + // Save any edits to this line + SaveEditedLine(); + + // If this is the end of the last line, consider whether to add a line + // instead + const LineInfoW *info = el_wline(m_editline); + if (m_current_line_index == m_input_lines.size() - 1 && + info->cursor == info->lastchar) { + if (m_is_input_complete_callback) { + auto lines = GetInputAsStringList(); + if (!m_is_input_complete_callback(this, lines, + m_is_input_complete_callback_baton)) { + return BreakLineCommand(ch); + } + + // The completion test is allowed to change the input lines when complete + m_input_lines.clear(); + for (unsigned index = 0; index < lines.GetSize(); index++) { +#if LLDB_EDITLINE_USE_WCHAR + m_input_lines.insert(m_input_lines.end(), + m_utf8conv.from_bytes(lines[index])); +#else + m_input_lines.insert(m_input_lines.end(), lines[index]); +#endif + } + } + } + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockEnd); + fprintf(m_output_file, "\n"); + m_editor_status = EditorStatus::Complete; + return CC_NEWLINE; +} + +unsigned char Editline::DeleteNextCharCommand(int ch) { + LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline)); + + // Just delete the next character normally if possible + if (info->cursor < info->lastchar) { + info->cursor++; + el_deletestr(m_editline, 1); + return CC_REFRESH; + } + + // Fail when at the end of the last line, except when ^D is pressed on the + // line is empty, in which case it is treated as EOF + if (m_current_line_index == m_input_lines.size() - 1) { + if (ch == 4 && info->buffer == info->lastchar) { + fprintf(m_output_file, "^D\n"); + m_editor_status = EditorStatus::EndOfInput; + return CC_EOF; + } + return CC_ERROR; + } + + // Prepare to combine this line with the one below + MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + + // Insert the next line of text at the cursor and restore the cursor position + const EditLineCharType *cursor = info->cursor; + el_winsertstr(m_editline, m_input_lines[m_current_line_index + 1].c_str()); + info->cursor = cursor; + SaveEditedLine(); + + // Delete the extra line + m_input_lines.erase(m_input_lines.begin() + m_current_line_index + 1); + + // Clear and repaint from this line on down + DisplayInput(m_current_line_index); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor); + return CC_REFRESH; +} + +unsigned char Editline::DeletePreviousCharCommand(int ch) { + LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline)); + + // Just delete the previous character normally when not at the start of a + // line + if (info->cursor > info->buffer) { + el_deletestr(m_editline, 1); + return CC_REFRESH; + } + + // No prior line and no prior character? Let the user know + if (m_current_line_index == 0) + return CC_ERROR; + + // No prior character, but prior line? Combine with the line above + SaveEditedLine(); + SetCurrentLine(m_current_line_index - 1); + auto priorLine = m_input_lines[m_current_line_index]; + m_input_lines.erase(m_input_lines.begin() + m_current_line_index); + m_input_lines[m_current_line_index] = + priorLine + m_input_lines[m_current_line_index]; + + // Repaint from the new line down + fprintf(m_output_file, ANSI_UP_N_ROWS ANSI_SET_COLUMN_N, + CountRowsForLine(priorLine), 1); + DisplayInput(m_current_line_index); + + // Put the cursor back where libedit expects it to be before returning to + // editing by telling libedit about the newly inserted text + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + el_winsertstr(m_editline, priorLine.c_str()); + return CC_REDISPLAY; +} + +unsigned char Editline::PreviousLineCommand(int ch) { + SaveEditedLine(); + + if (m_current_line_index == 0) { + return RecallHistory(HistoryOperation::Older); + } + + // Start from a known location + MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + + // Treat moving up from a blank last line as a deletion of that line + if (m_current_line_index == m_input_lines.size() - 1 && IsOnlySpaces()) { + m_input_lines.erase(m_input_lines.begin() + m_current_line_index); + fprintf(m_output_file, ANSI_CLEAR_BELOW); + } + + SetCurrentLine(m_current_line_index - 1); + fprintf(m_output_file, ANSI_UP_N_ROWS ANSI_SET_COLUMN_N, + CountRowsForLine(m_input_lines[m_current_line_index]), 1); + return CC_NEWLINE; +} + +unsigned char Editline::NextLineCommand(int ch) { + SaveEditedLine(); + + // Handle attempts to move down from the last line + if (m_current_line_index == m_input_lines.size() - 1) { + // Don't add an extra line if the existing last line is blank, move through + // history instead + if (IsOnlySpaces()) { + return RecallHistory(HistoryOperation::Newer); + } + + // Determine indentation for the new line + int indentation = 0; + if (m_fix_indentation_callback) { + StringList lines = GetInputAsStringList(); + lines.AppendString(""); + indentation = m_fix_indentation_callback( + this, lines, 0, m_fix_indentation_callback_baton); + } + m_input_lines.insert( + m_input_lines.end(), + EditLineStringType(indentation, EditLineCharType(' '))); + } + + // Move down past the current line using newlines to force scrolling if + // needed + SetCurrentLine(m_current_line_index + 1); + const LineInfoW *info = el_wline(m_editline); + int cursor_position = (int)((info->cursor - info->buffer) + GetPromptWidth()); + int cursor_row = cursor_position / m_terminal_width; + for (int line_count = 0; line_count < m_current_line_rows - cursor_row; + line_count++) { + fprintf(m_output_file, "\n"); + } + return CC_NEWLINE; +} + +unsigned char Editline::PreviousHistoryCommand(int ch) { + SaveEditedLine(); + + return RecallHistory(HistoryOperation::Older); +} + +unsigned char Editline::NextHistoryCommand(int ch) { + SaveEditedLine(); + + return RecallHistory(HistoryOperation::Newer); +} + +unsigned char Editline::FixIndentationCommand(int ch) { + if (!m_fix_indentation_callback) + return CC_NORM; + + // Insert the character typed before proceeding + EditLineCharType inserted[] = {(EditLineCharType)ch, 0}; + el_winsertstr(m_editline, inserted); + LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline)); + int cursor_position = info->cursor - info->buffer; + + // Save the edits and determine the correct indentation level + SaveEditedLine(); + StringList lines = GetInputAsStringList(m_current_line_index + 1); + int indent_correction = m_fix_indentation_callback( + this, lines, cursor_position, m_fix_indentation_callback_baton); + + // If it is already correct no special work is needed + if (indent_correction == 0) + return CC_REFRESH; + + // Change the indentation level of the line + std::string currentLine = lines.GetStringAtIndex(m_current_line_index); + if (indent_correction > 0) { + currentLine = currentLine.insert(0, indent_correction, ' '); + } else { + currentLine = currentLine.erase(0, -indent_correction); + } +#if LLDB_EDITLINE_USE_WCHAR + m_input_lines[m_current_line_index] = m_utf8conv.from_bytes(currentLine); +#else + m_input_lines[m_current_line_index] = currentLine; +#endif + + // Update the display to reflect the change + MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + DisplayInput(m_current_line_index); + + // Reposition the cursor back on the original line and prepare to restart + // editing with a new cursor position + SetCurrentLine(m_current_line_index); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + m_revert_cursor_index = cursor_position + indent_correction; + return CC_NEWLINE; +} + +unsigned char Editline::RevertLineCommand(int ch) { + el_winsertstr(m_editline, m_input_lines[m_current_line_index].c_str()); + if (m_revert_cursor_index >= 0) { + LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline)); + info->cursor = info->buffer + m_revert_cursor_index; + if (info->cursor > info->lastchar) { + info->cursor = info->lastchar; + } + m_revert_cursor_index = -1; + } + return CC_REFRESH; +} + +unsigned char Editline::BufferStartCommand(int ch) { + SaveEditedLine(); + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart); + SetCurrentLine(0); + m_revert_cursor_index = 0; + return CC_NEWLINE; +} + +unsigned char Editline::BufferEndCommand(int ch) { + SaveEditedLine(); + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockEnd); + SetCurrentLine((int)m_input_lines.size() - 1); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + return CC_NEWLINE; +} + +/// Prints completions and their descriptions to the given file. Only the +/// completions in the interval [start, end) are printed. +static void +PrintCompletion(FILE *output_file, + llvm::ArrayRef<CompletionResult::Completion> results, + size_t max_len) { + for (const CompletionResult::Completion &c : results) { + fprintf(output_file, "\t%-*s", (int)max_len, c.GetCompletion().c_str()); + if (!c.GetDescription().empty()) + fprintf(output_file, " -- %s", c.GetDescription().c_str()); + fprintf(output_file, "\n"); + } +} + +static void +DisplayCompletions(::EditLine *editline, FILE *output_file, + llvm::ArrayRef<CompletionResult::Completion> results) { + assert(!results.empty()); + + fprintf(output_file, "\n" ANSI_CLEAR_BELOW "Available completions:\n"); + const size_t page_size = 40; + bool all = false; + + auto longest = + std::max_element(results.begin(), results.end(), [](auto &c1, auto &c2) { + return c1.GetCompletion().size() < c2.GetCompletion().size(); + }); + + const size_t max_len = longest->GetCompletion().size(); + + if (results.size() < page_size) { + PrintCompletion(output_file, results, max_len); + return; + } + + size_t cur_pos = 0; + while (cur_pos < results.size()) { + size_t remaining = results.size() - cur_pos; + size_t next_size = all ? remaining : std::min(page_size, remaining); + + PrintCompletion(output_file, results.slice(cur_pos, next_size), max_len); + + cur_pos += next_size; + + if (cur_pos >= results.size()) + break; + + fprintf(output_file, "More (Y/n/a): "); + char reply = 'n'; + int got_char = el_getc(editline, &reply); + fprintf(output_file, "\n"); + if (got_char == -1 || reply == 'n') + break; + if (reply == 'a') + all = true; + } +} + +unsigned char Editline::TabCommand(int ch) { + if (m_completion_callback == nullptr) + return CC_ERROR; + + const LineInfo *line_info = el_line(m_editline); + + llvm::StringRef line(line_info->buffer, + line_info->lastchar - line_info->buffer); + unsigned cursor_index = line_info->cursor - line_info->buffer; + CompletionResult result; + CompletionRequest request(line, cursor_index, result); + + m_completion_callback(request, m_completion_callback_baton); + + llvm::ArrayRef<CompletionResult::Completion> results = result.GetResults(); + + StringList completions; + result.GetMatches(completions); + + if (results.size() == 0) + return CC_ERROR; + + if (results.size() == 1) { + CompletionResult::Completion completion = results.front(); + switch (completion.GetMode()) { + case CompletionMode::Normal: { + std::string to_add = completion.GetCompletion(); + to_add = to_add.substr(request.GetCursorArgumentPrefix().size()); + if (request.GetParsedArg().IsQuoted()) + to_add.push_back(request.GetParsedArg().GetQuoteChar()); + to_add.push_back(' '); + el_insertstr(m_editline, to_add.c_str()); + break; + } + case CompletionMode::Partial: { + std::string to_add = completion.GetCompletion(); + to_add = to_add.substr(request.GetCursorArgumentPrefix().size()); + el_insertstr(m_editline, to_add.c_str()); + break; + } + case CompletionMode::RewriteLine: { + el_deletestr(m_editline, line_info->cursor - line_info->buffer); + el_insertstr(m_editline, completion.GetCompletion().c_str()); + break; + } + } + return CC_REDISPLAY; + } + + // If we get a longer match display that first. + std::string longest_prefix = completions.LongestCommonPrefix(); + if (!longest_prefix.empty()) + longest_prefix = + longest_prefix.substr(request.GetCursorArgumentPrefix().size()); + if (!longest_prefix.empty()) { + el_insertstr(m_editline, longest_prefix.c_str()); + return CC_REDISPLAY; + } + + DisplayCompletions(m_editline, m_output_file, results); + + DisplayInput(); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor); + return CC_REDISPLAY; +} + +void Editline::ConfigureEditor(bool multiline) { + if (m_editline && m_multiline_enabled == multiline) + return; + m_multiline_enabled = multiline; + + if (m_editline) { + // Disable edit mode to stop the terminal from flushing all input during + // the call to el_end() since we expect to have multiple editline instances + // in this program. + el_set(m_editline, EL_EDITMODE, 0); + el_end(m_editline); + } + + m_editline = + el_init(m_editor_name.c_str(), m_input_file, m_output_file, m_error_file); + TerminalSizeChanged(); + + if (m_history_sp && m_history_sp->IsValid()) { + if (!m_history_sp->Load()) { + fputs("Could not load history file\n.", m_output_file); + } + el_wset(m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr()); + } + el_set(m_editline, EL_CLIENTDATA, this); + el_set(m_editline, EL_SIGNAL, 0); + el_set(m_editline, EL_EDITOR, "emacs"); + el_set(m_editline, EL_PROMPT, + (EditlinePromptCallbackType)([](EditLine *editline) { + return Editline::InstanceFor(editline)->Prompt(); + })); + + el_wset(m_editline, EL_GETCFN, (EditlineGetCharCallbackType)([]( + EditLine *editline, EditLineGetCharType *c) { + return Editline::InstanceFor(editline)->GetCharacter(c); + })); + + // Commands used for multiline support, registered whether or not they're + // used + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-break-line"), + EditLineConstString("Insert a line break"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->BreakLineCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-end-or-add-line"), + EditLineConstString("End editing or continue when incomplete"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->EndOrAddLineCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-delete-next-char"), + EditLineConstString("Delete next character"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->DeleteNextCharCommand(ch); + })); + el_wset( + m_editline, EL_ADDFN, EditLineConstString("lldb-delete-previous-char"), + EditLineConstString("Delete previous character"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->DeletePreviousCharCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-previous-line"), + EditLineConstString("Move to previous line"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->PreviousLineCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-next-line"), + EditLineConstString("Move to next line"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->NextLineCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-previous-history"), + EditLineConstString("Move to previous history"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->PreviousHistoryCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-next-history"), + EditLineConstString("Move to next history"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->NextHistoryCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-buffer-start"), + EditLineConstString("Move to start of buffer"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->BufferStartCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-buffer-end"), + EditLineConstString("Move to end of buffer"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->BufferEndCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-fix-indentation"), + EditLineConstString("Fix line indentation"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->FixIndentationCommand(ch); + })); + + // Register the complete callback under two names for compatibility with + // older clients using custom .editrc files (largely because libedit has a + // bad bug where if you have a bind command that tries to bind to a function + // name that doesn't exist, it can corrupt the heap and crash your process + // later.) + EditlineCommandCallbackType complete_callback = [](EditLine *editline, + int ch) { + return Editline::InstanceFor(editline)->TabCommand(ch); + }; + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-complete"), + EditLineConstString("Invoke completion"), complete_callback); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb_complete"), + EditLineConstString("Invoke completion"), complete_callback); + + // General bindings we don't mind being overridden + if (!multiline) { + el_set(m_editline, EL_BIND, "^r", "em-inc-search-prev", + NULL); // Cycle through backwards search, entering string + } + el_set(m_editline, EL_BIND, "^w", "ed-delete-prev-word", + NULL); // Delete previous word, behave like bash in emacs mode + el_set(m_editline, EL_BIND, "\t", "lldb-complete", + NULL); // Bind TAB to auto complete + + // Allow ctrl-left-arrow and ctrl-right-arrow for navigation, behave like + // bash in emacs mode. + el_set(m_editline, EL_BIND, ESCAPE "[1;5C", "em-next-word", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[1;5D", "ed-prev-word", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[5C", "em-next-word", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[5D", "ed-prev-word", NULL); + el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[C", "em-next-word", NULL); + el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[D", "ed-prev-word", NULL); + + // Allow user-specific customization prior to registering bindings we + // absolutely require + el_source(m_editline, nullptr); + + // Register an internal binding that external developers shouldn't use + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-revert-line"), + EditLineConstString("Revert line to saved state"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->RevertLineCommand(ch); + })); + + // Register keys that perform auto-indent correction + if (m_fix_indentation_callback && m_fix_indentation_callback_chars) { + char bind_key[2] = {0, 0}; + const char *indent_chars = m_fix_indentation_callback_chars; + while (*indent_chars) { + bind_key[0] = *indent_chars; + el_set(m_editline, EL_BIND, bind_key, "lldb-fix-indentation", NULL); + ++indent_chars; + } + } + + // Multi-line editor bindings + if (multiline) { + el_set(m_editline, EL_BIND, "\n", "lldb-end-or-add-line", NULL); + el_set(m_editline, EL_BIND, "\r", "lldb-end-or-add-line", NULL); + el_set(m_editline, EL_BIND, ESCAPE "\n", "lldb-break-line", NULL); + el_set(m_editline, EL_BIND, ESCAPE "\r", "lldb-break-line", NULL); + el_set(m_editline, EL_BIND, "^p", "lldb-previous-line", NULL); + el_set(m_editline, EL_BIND, "^n", "lldb-next-line", NULL); + el_set(m_editline, EL_BIND, "^?", "lldb-delete-previous-char", NULL); + el_set(m_editline, EL_BIND, "^d", "lldb-delete-next-char", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[3~", "lldb-delete-next-char", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[\\^", "lldb-revert-line", NULL); + + // Editor-specific bindings + if (IsEmacs()) { + el_set(m_editline, EL_BIND, ESCAPE "<", "lldb-buffer-start", NULL); + el_set(m_editline, EL_BIND, ESCAPE ">", "lldb-buffer-end", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[A", "lldb-previous-line", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[B", "lldb-next-line", NULL); + el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[A", "lldb-previous-history", + NULL); + el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[B", "lldb-next-history", + NULL); + el_set(m_editline, EL_BIND, ESCAPE "[1;3A", "lldb-previous-history", + NULL); + el_set(m_editline, EL_BIND, ESCAPE "[1;3B", "lldb-next-history", NULL); + } else { + el_set(m_editline, EL_BIND, "^H", "lldb-delete-previous-char", NULL); + + el_set(m_editline, EL_BIND, "-a", ESCAPE "[A", "lldb-previous-line", + NULL); + el_set(m_editline, EL_BIND, "-a", ESCAPE "[B", "lldb-next-line", NULL); + el_set(m_editline, EL_BIND, "-a", "x", "lldb-delete-next-char", NULL); + el_set(m_editline, EL_BIND, "-a", "^H", "lldb-delete-previous-char", + NULL); + el_set(m_editline, EL_BIND, "-a", "^?", "lldb-delete-previous-char", + NULL); + + // Escape is absorbed exiting edit mode, so re-register important + // sequences without the prefix + el_set(m_editline, EL_BIND, "-a", "[A", "lldb-previous-line", NULL); + el_set(m_editline, EL_BIND, "-a", "[B", "lldb-next-line", NULL); + el_set(m_editline, EL_BIND, "-a", "[\\^", "lldb-revert-line", NULL); + } + } +} + +// Editline public methods + +Editline *Editline::InstanceFor(EditLine *editline) { + Editline *editor; + el_get(editline, EL_CLIENTDATA, &editor); + return editor; +} + +Editline::Editline(const char *editline_name, FILE *input_file, + FILE *output_file, FILE *error_file, bool color_prompts) + : m_editor_status(EditorStatus::Complete), m_color_prompts(color_prompts), + m_input_file(input_file), m_output_file(output_file), + m_error_file(error_file), m_input_connection(fileno(input_file), false) { + // Get a shared history instance + m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name; + m_history_sp = EditlineHistory::GetHistory(m_editor_name); + +#ifdef USE_SETUPTERM_WORKAROUND + if (m_output_file) { + const int term_fd = fileno(m_output_file); + if (term_fd != -1) { + static std::mutex *g_init_terminal_fds_mutex_ptr = nullptr; + static std::set<int> *g_init_terminal_fds_ptr = nullptr; + static llvm::once_flag g_once_flag; + llvm::call_once(g_once_flag, [&]() { + g_init_terminal_fds_mutex_ptr = + new std::mutex(); // NOTE: Leak to avoid C++ destructor chain issues + g_init_terminal_fds_ptr = new std::set<int>(); // NOTE: Leak to avoid + // C++ destructor chain + // issues + }); + + // We must make sure to initialize the terminal a given file descriptor + // only once. If we do this multiple times, we start leaking memory. + std::lock_guard<std::mutex> guard(*g_init_terminal_fds_mutex_ptr); + if (g_init_terminal_fds_ptr->find(term_fd) == + g_init_terminal_fds_ptr->end()) { + g_init_terminal_fds_ptr->insert(term_fd); + setupterm((char *)0, term_fd, (int *)0); + } + } + } +#endif +} + +Editline::~Editline() { + if (m_editline) { + // Disable edit mode to stop the terminal from flushing all input during + // the call to el_end() since we expect to have multiple editline instances + // in this program. + el_set(m_editline, EL_EDITMODE, 0); + el_end(m_editline); + m_editline = nullptr; + } + + // EditlineHistory objects are sometimes shared between multiple Editline + // instances with the same program name. So just release our shared pointer + // and if we are the last owner, it will save the history to the history save + // file automatically. + m_history_sp.reset(); +} + +void Editline::SetPrompt(const char *prompt) { + m_set_prompt = prompt == nullptr ? "" : prompt; +} + +void Editline::SetContinuationPrompt(const char *continuation_prompt) { + m_set_continuation_prompt = + continuation_prompt == nullptr ? "" : continuation_prompt; +} + +void Editline::TerminalSizeChanged() { + if (m_editline != nullptr) { + el_resize(m_editline); + int columns; + // This function is documenting as taking (const char *, void *) for the + // vararg part, but in reality in was consuming arguments until the first + // null pointer. This was fixed in libedit in April 2019 + // <http://mail-index.netbsd.org/source-changes/2019/04/26/msg105454.html>, + // but we're keeping the workaround until a version with that fix is more + // widely available. + if (el_get(m_editline, EL_GETTC, "co", &columns, nullptr) == 0) { + m_terminal_width = columns; + if (m_current_line_rows != -1) { + const LineInfoW *info = el_wline(m_editline); + int lineLength = + (int)((info->lastchar - info->buffer) + GetPromptWidth()); + m_current_line_rows = (lineLength / columns) + 1; + } + } else { + m_terminal_width = INT_MAX; + m_current_line_rows = 1; + } + } +} + +const char *Editline::GetPrompt() { return m_set_prompt.c_str(); } + +uint32_t Editline::GetCurrentLine() { return m_current_line_index; } + +bool Editline::Interrupt() { + bool result = true; + std::lock_guard<std::mutex> guard(m_output_mutex); + if (m_editor_status == EditorStatus::Editing) { + fprintf(m_output_file, "^C\n"); + result = m_input_connection.InterruptRead(); + } + m_editor_status = EditorStatus::Interrupted; + return result; +} + +bool Editline::Cancel() { + bool result = true; + std::lock_guard<std::mutex> guard(m_output_mutex); + if (m_editor_status == EditorStatus::Editing) { + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart); + fprintf(m_output_file, ANSI_CLEAR_BELOW); + result = m_input_connection.InterruptRead(); + } + m_editor_status = EditorStatus::Interrupted; + return result; +} + +void Editline::SetAutoCompleteCallback(CompleteCallbackType callback, + void *baton) { + m_completion_callback = callback; + m_completion_callback_baton = baton; +} + +void Editline::SetIsInputCompleteCallback(IsInputCompleteCallbackType callback, + void *baton) { + m_is_input_complete_callback = callback; + m_is_input_complete_callback_baton = baton; +} + +bool Editline::SetFixIndentationCallback(FixIndentationCallbackType callback, + void *baton, + const char *indent_chars) { + m_fix_indentation_callback = callback; + m_fix_indentation_callback_baton = baton; + m_fix_indentation_callback_chars = indent_chars; + return false; +} + +bool Editline::GetLine(std::string &line, bool &interrupted) { + ConfigureEditor(false); + m_input_lines = std::vector<EditLineStringType>(); + m_input_lines.insert(m_input_lines.begin(), EditLineConstString("")); + + std::lock_guard<std::mutex> guard(m_output_mutex); + + lldbassert(m_editor_status != EditorStatus::Editing); + if (m_editor_status == EditorStatus::Interrupted) { + m_editor_status = EditorStatus::Complete; + interrupted = true; + return true; + } + + SetCurrentLine(0); + m_in_history = false; + m_editor_status = EditorStatus::Editing; + m_revert_cursor_index = -1; + + int count; + auto input = el_wgets(m_editline, &count); + + interrupted = m_editor_status == EditorStatus::Interrupted; + if (!interrupted) { + if (input == nullptr) { + fprintf(m_output_file, "\n"); + m_editor_status = EditorStatus::EndOfInput; + } else { + m_history_sp->Enter(input); +#if LLDB_EDITLINE_USE_WCHAR + line = m_utf8conv.to_bytes(SplitLines(input)[0]); +#else + line = SplitLines(input)[0]; +#endif + m_editor_status = EditorStatus::Complete; + } + } + return m_editor_status != EditorStatus::EndOfInput; +} + +bool Editline::GetLines(int first_line_number, StringList &lines, + bool &interrupted) { + ConfigureEditor(true); + + // Print the initial input lines, then move the cursor back up to the start + // of input + SetBaseLineNumber(first_line_number); + m_input_lines = std::vector<EditLineStringType>(); + m_input_lines.insert(m_input_lines.begin(), EditLineConstString("")); + + std::lock_guard<std::mutex> guard(m_output_mutex); + // Begin the line editing loop + DisplayInput(); + SetCurrentLine(0); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::BlockStart); + m_editor_status = EditorStatus::Editing; + m_in_history = false; + + m_revert_cursor_index = -1; + while (m_editor_status == EditorStatus::Editing) { + int count; + m_current_line_rows = -1; + el_wpush(m_editline, EditLineConstString( + "\x1b[^")); // Revert to the existing line content + el_wgets(m_editline, &count); + } + + interrupted = m_editor_status == EditorStatus::Interrupted; + if (!interrupted) { + // Save the completed entry in history before returning + m_history_sp->Enter(CombineLines(m_input_lines).c_str()); + + lines = GetInputAsStringList(); + } + return m_editor_status != EditorStatus::EndOfInput; +} + +void Editline::PrintAsync(Stream *stream, const char *s, size_t len) { + std::lock_guard<std::mutex> guard(m_output_mutex); + if (m_editor_status == EditorStatus::Editing) { + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart); + fprintf(m_output_file, ANSI_CLEAR_BELOW); + } + stream->Write(s, len); + stream->Flush(); + if (m_editor_status == EditorStatus::Editing) { + DisplayInput(); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor); + } +} + +bool Editline::CompleteCharacter(char ch, EditLineGetCharType &out) { +#if !LLDB_EDITLINE_USE_WCHAR + if (ch == (char)EOF) + return false; + + out = (unsigned char)ch; + return true; +#else + std::codecvt_utf8<wchar_t> cvt; + llvm::SmallString<4> input; + for (;;) { + const char *from_next; + wchar_t *to_next; + std::mbstate_t state = std::mbstate_t(); + input.push_back(ch); + switch (cvt.in(state, input.begin(), input.end(), from_next, &out, &out + 1, + to_next)) { + case std::codecvt_base::ok: + return out != (int)WEOF; + + case std::codecvt_base::error: + case std::codecvt_base::noconv: + return false; + + case std::codecvt_base::partial: + lldb::ConnectionStatus status; + size_t read_count = m_input_connection.Read( + &ch, 1, std::chrono::seconds(0), status, nullptr); + if (read_count == 0) + return false; + break; + } + } +#endif +} |