aboutsummaryrefslogtreecommitdiff
path: root/source/Host/common/Editline.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/Host/common/Editline.cpp')
-rw-r--r--source/Host/common/Editline.cpp1735
1 files changed, 1131 insertions, 604 deletions
diff --git a/source/Host/common/Editline.cpp b/source/Host/common/Editline.cpp
index 7af9f39a7863..b82fbea90c6c 100644
--- a/source/Host/common/Editline.cpp
+++ b/source/Host/common/Editline.cpp
@@ -7,527 +7,913 @@
//
//===----------------------------------------------------------------------===//
+#include <iomanip>
+#include <iostream>
+#include <limits.h>
#include "lldb/Host/Editline.h"
-
+#include "lldb/Host/ConnectionFileDescriptor.h"
#include "lldb/Core/Error.h"
-#include "lldb/Core/StreamString.h"
#include "lldb/Core/StringList.h"
+#include "lldb/Core/StreamString.h"
+#include "lldb/Host/FileSpec.h"
+#include "lldb/Host/FileSystem.h"
#include "lldb/Host/Host.h"
+#include "lldb/Host/Mutex.h"
-#include <limits.h>
-
-using namespace lldb;
using namespace lldb_private;
+using namespace lldb_private::line_editor;
-namespace lldb_private {
- typedef std::weak_ptr<EditlineHistory> EditlineHistoryWP;
-
-
- // EditlineHistory objects are sometimes shared between multiple
- // Editline instances with the same program name. This class allows
- // multiple editline instances to
- //
-
- 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 (NULL),
- m_event (),
- m_prefix (prefix),
- m_path ()
+// 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;
+}
+
+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)
{
- m_history = ::history_init();
- ::history (m_history, &m_event, H_SETSIZE, size);
- if (unique_entries)
- ::history (m_history, &m_event, H_SETUNIQUE, 1);
+ 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);
+ fd_set fds;
+ FD_ZERO (&fds);
+ FD_SET (fd, &fds);
+ timeval timeout = { 0, 0 };
+ return select (fd + 1, &fds, NULL, NULL, &timeout);
+}
+
+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.
- const char *
- GetHistoryFilePath()
+ class EditlineHistory
{
- if (m_path.empty() && m_history && !m_prefix.empty())
+ 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 (NULL),
+ m_event (),
+ m_prefix (prefix),
+ m_path ()
{
- char history_path[PATH_MAX];
- ::snprintf (history_path, sizeof(history_path), "~/.%s-history", m_prefix.c_str());
- m_path = std::move(FileSpec(history_path, true).GetPath());
+ 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()
+ {
+ if (m_path.empty() && m_history && !m_prefix.empty())
+ {
+ std::string parent_path = FileSpec ("~/.lldb", true).GetPath();
+ char history_path[PATH_MAX];
+ if (FileSystem::MakeDirectory(parent_path.c_str(), lldb::eFilePermissionsDirectoryDefault).Success())
+ {
+ snprintf (history_path, sizeof (history_path), "~/.lldb/%s-history", m_prefix.c_str());
+ }
+ else
+ {
+ snprintf (history_path, sizeof (history_path), "~/%s-widehistory", m_prefix.c_str());
+ }
+ m_path = std::move (FileSpec (history_path, true).GetPath());
+ }
+ if (m_path.empty())
+ return NULL;
+ return m_path.c_str();
}
- if (m_path.empty())
- return NULL;
- return m_path.c_str();
- }
-
- public:
-
- ~EditlineHistory()
- {
- Save ();
- if (m_history)
+ public:
+
+ ~EditlineHistory()
{
- ::history_end (m_history);
- m_history = NULL;
+ Save();
+
+ if (m_history)
+ {
+ history_wend (m_history);
+ m_history = NULL;
+ }
}
- }
-
- static EditlineHistorySP
- GetHistory (const std::string &prefix)
- {
- typedef std::map<std::string, EditlineHistoryWP> WeakHistoryMap;
- static Mutex g_mutex(Mutex::eMutexTypeRecursive);
- static WeakHistoryMap g_weak_map;
- Mutex::Locker locker (g_mutex);
- WeakHistoryMap::const_iterator pos = g_weak_map.find (prefix);
- EditlineHistorySP history_sp;
- if (pos != g_weak_map.end())
+
+ static EditlineHistorySP
+ GetHistory (const std::string &prefix)
{
- history_sp = pos->second.lock();
- if (history_sp)
- return history_sp;
- g_weak_map.erase(pos);
+ typedef std::map<std::string, EditlineHistoryWP> WeakHistoryMap;
+ static Mutex g_mutex (Mutex::eMutexTypeRecursive);
+ static WeakHistoryMap g_weak_map;
+ Mutex::Locker locker (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;
}
- history_sp.reset(new EditlineHistory(prefix, 800, true));
- g_weak_map[prefix] = history_sp;
- return history_sp;
- }
-
- bool IsValid() const
- {
- return m_history != NULL;
- }
-
- ::History *
- GetHistoryPtr ()
- {
- return m_history;
- }
-
- void
- Enter (const char *line_cstr)
- {
- if (m_history)
- ::history (m_history, &m_event, H_ENTER, line_cstr);
- }
-
- bool
- Load ()
- {
- if (m_history)
+
+ bool IsValid() const
{
- const char *path = GetHistoryFilePath();
- if (path)
+ return m_history != NULL;
+ }
+
+ 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)
{
- ::history (m_history, &m_event, H_LOAD, path);
- return true;
+ const char *path = GetHistoryFilePath();
+ if (path)
+ {
+ history_w (m_history, &m_event, H_LOAD, path);
+ return true;
+ }
}
+ return false;
}
- return false;
- }
-
- bool
- Save ()
- {
- if (m_history)
+
+ bool
+ Save ()
{
- const char *path = GetHistoryFilePath();
- if (path)
+ if (m_history)
{
- ::history (m_history, &m_event, H_SAVE, path);
- return true;
+ const char *path = GetHistoryFilePath();
+ if (path)
+ {
+ history_w (m_history, &m_event, H_SAVE, path);
+ return true;
+ }
}
+ return false;
}
- return false;
- }
-
- protected:
- ::History *m_history; // The history object
- ::HistEvent 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
- };
+
+ 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
+//------------------------------------------------------------------
-static const char k_prompt_escape_char = '\1';
-
-Editline::Editline (const char *prog, // prog can't be NULL
- const char *prompt, // can be NULL for no prompt
- bool configure_for_multiline,
- FILE *fin,
- FILE *fout,
- FILE *ferr) :
- m_editline (NULL),
- m_history_sp (),
- m_prompt (),
- m_lines_prompt (),
- m_getting_char (false),
- m_completion_callback (NULL),
- m_completion_callback_baton (NULL),
- m_line_complete_callback (NULL),
- m_line_complete_callback_baton (NULL),
- m_lines_command (Command::None),
- m_line_offset (0),
- m_lines_curr_line (0),
- m_lines_max_line (0),
- m_file (fileno(fin), false),
- m_prompt_with_line_numbers (false),
- m_getting_line (false),
- m_got_eof (false),
- m_interrupted (false)
-{
- if (prog && prog[0])
- {
- m_editline = ::el_init(prog, fin, fout, ferr);
-
- // Get a shared history instance
- m_history_sp = EditlineHistory::GetHistory(prog);
+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 = ": ";
}
- else
+ std::string continuation_prompt = prompt;
+ if (m_set_continuation_prompt.length() > 0)
{
- m_editline = ::el_init("lldb-tmp", fin, fout, ferr);
+ 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 (prompt && prompt[0])
- SetPrompt (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;
+}
- //::el_set (m_editline, EL_BIND, "^[[A", NULL); // Print binding for up arrow key
- //::el_set (m_editline, EL_BIND, "^[[B", NULL); // Print binding for up down key
+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();
+}
- assert (m_editline);
- ::el_set (m_editline, EL_CLIENTDATA, this);
+bool
+Editline::IsEmacs()
+{
+ const char * editor;
+ el_get (m_editline, EL_EDITOR, &editor);
+ return editor[0] == 'e';
+}
- // only defined for newer versions of editline
-#ifdef EL_PROMPT_ESC
- ::el_set (m_editline, EL_PROMPT_ESC, GetPromptCallback, k_prompt_escape_char);
-#else
- // fall back on old prompt setting code
- ::el_set (m_editline, EL_PROMPT, GetPromptCallback);
-#endif
- ::el_set (m_editline, EL_EDITOR, "emacs");
- if (m_history_sp && m_history_sp->IsValid())
+bool
+Editline::IsOnlySpaces()
+{
+ const LineInfoW * info = el_wline (m_editline);
+ for (const EditLineCharType * character = info->buffer; character < info->lastchar; character++)
{
- ::el_set (m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr());
+ if (*character != ' ')
+ return false;
}
- ::el_set (m_editline, EL_ADDFN, "lldb-complete", "Editline completion function", Editline::CallbackComplete);
- // Keep old "lldb_complete" mapping for older clients that used this in their .editrc. editline also
- // has a bad bug where if you have a bind command that tries to bind to a function name that doesn't
- // exist, it will corrupt the heap and probably crash your process later.
- ::el_set (m_editline, EL_ADDFN, "lldb_complete", "Editline completion function", Editline::CallbackComplete);
- ::el_set (m_editline, EL_ADDFN, "lldb-edit-prev-line", "Editline edit prev line", Editline::CallbackEditPrevLine);
- ::el_set (m_editline, EL_ADDFN, "lldb-edit-next-line", "Editline edit next line", Editline::CallbackEditNextLine);
+ return true;
+}
- ::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 does.
- ::el_set (m_editline, EL_BIND, "\033[3~", "ed-delete-next-char", NULL); // Fix the delete key.
- ::el_set (m_editline, EL_BIND, "\t", "lldb-complete", NULL); // Bind TAB to be auto complete
-
- if (configure_for_multiline)
+int
+Editline::GetLineIndexForLocation (CursorLocation location, int cursor_row)
+{
+ int line = 0;
+ if (location == CursorLocation::EditingPrompt || location == CursorLocation::BlockEnd ||
+ location == CursorLocation::EditingCursor)
{
- // Use escape sequences for control characters due to bugs in editline
- // where "-k up" and "-k down" don't always work.
- ::el_set (m_editline, EL_BIND, "^[[A", "lldb-edit-prev-line", NULL); // Map up arrow
- ::el_set (m_editline, EL_BIND, "^[[B", "lldb-edit-next-line", NULL); // Map down arrow
- // Bindings for next/prev history
- ::el_set (m_editline, EL_BIND, "^P", "ed-prev-history", NULL); // Map up arrow
- ::el_set (m_editline, EL_BIND, "^N", "ed-next-history", NULL); // Map down arrow
+ 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;
+ }
}
- else
+ 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)
{
- // Use escape sequences for control characters due to bugs in editline
- // where "-k up" and "-k down" don't always work.
- ::el_set (m_editline, EL_BIND, "^[[A", "ed-prev-history", NULL); // Map up arrow
- ::el_set (m_editline, EL_BIND, "^[[B", "ed-next-history", NULL); // Map down arrow
+ fprintf (m_output_file, (toLine > fromLine) ? ANSI_DOWN_N_ROWS : ANSI_UP_N_ROWS, std::abs (toLine - fromLine));
}
- // Source $PWD/.editrc then $HOME/.editrc
- ::el_source (m_editline, NULL);
-
- // Always read through our callback function so we don't read
- // stuff we aren't supposed to. This also stops the extra echoing
- // that can happen when you have more input than editline can handle
- // at once.
- SetGetCharCallback(GetCharFromInputFileCallback);
-
- LoadHistory();
+ // 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)
+ {
+ toColumn = ((m_input_lines[m_input_lines.size() - 1].length() + GetPromptWidth()) % 80) + 1;
+ }
+ fprintf (m_output_file, ANSI_SET_COLUMN_N, toColumn);
}
-Editline::~Editline()
+void
+Editline::DisplayInput (int firstIndex)
{
- // 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();
+ 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 : "";
- // 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);
+ 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");
+ }
+}
- ::el_end(m_editline);
- m_editline = NULL;
+
+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::SetGetCharCallback (GetCharCallbackType callback)
+Editline::SaveEditedLine()
{
- ::el_set (m_editline, EL_GETCFN, callback);
+ const LineInfoW * info = el_wline (m_editline);
+ m_input_lines[m_current_line_index] = EditLineStringType (info->buffer, info->lastchar - info->buffer);
}
-bool
-Editline::LoadHistory ()
+StringList
+Editline::GetInputAsStringList(int line_count)
{
- if (m_history_sp)
- return m_history_sp->Load();
- return false;
+ 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;
}
-bool
-Editline::SaveHistory ()
+unsigned char
+Editline::RecallHistory (bool earlier)
{
- if (m_history_sp)
- return m_history_sp->Save();
- return false;
-}
+ 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)
+ {
+ if (earlier == false)
+ return CC_ERROR; // Can't go newer than the "live" entry
+ if (history_w (pHistory, &history_event, H_FIRST) == -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;
+ }
+ else
+ {
+ if (history_w (pHistory, &history_event, earlier ? H_NEXT : H_PREV) == -1)
+ {
+ // Can't move earlier than the earliest entry
+ if (earlier)
+ return CC_ERROR;
+ // ... but moving to newer than the newest yields the "live" entry
+ new_input_lines = m_live_history_lines;
+ m_in_history = false;
+ }
+ }
+
+ // If we're pulling the lines from history, split them apart
+ if (m_in_history)
+ new_input_lines = SplitLines (history_event.str);
-Error
-Editline::PrivateGetLine(std::string &line)
+ // 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
+ SetCurrentLine (m_current_line_index = earlier ? (int)m_input_lines.size() - 1 : 0);
+ MoveCursor (CursorLocation::BlockEnd, CursorLocation::EditingPrompt);
+ return CC_NEWLINE;
+}
+
+int
+Editline::GetCharacter (EditLineCharType * c)
{
- Error error;
- if (m_interrupted)
+ 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)
{
- error.SetErrorString("interrupted");
- return error;
+ 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;
}
- line.clear();
- if (m_editline != NULL)
+ if (m_multiline_enabled)
{
- int line_len = 0;
- // Call el_gets to prompt the user and read the user's input.
- const char *line_cstr = ::el_gets (m_editline, &line_len);
-
- static int save_errno = (line_len < 0) ? errno : 0;
-
- if (save_errno != 0)
+ // 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)
{
- error.SetError(save_errno, eErrorTypePOSIX);
+ // 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);
}
- else if (line_cstr)
+ m_current_line_rows = new_line_rows;
+ }
+
+ // Read an actual character
+ while (true)
+ {
+ lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess;
+ char ch = 0;
+ m_editor_getting_char = true;
+ int read_count = m_input_connection.Read(&ch, 1, UINT32_MAX, status, NULL);
+ m_editor_getting_char = false;
+ if (read_count)
{
- // Decrement the length so we don't have newline characters in "line" for when
- // we assign the cstr into the std::string
- llvm::StringRef line_ref (line_cstr);
- line_ref = line_ref.rtrim("\n\r");
-
- if (!line_ref.empty() && !m_interrupted)
+#if LLDB_EDITLINE_USE_WCHAR
+ // After the initial interruptible read, this is guaranteed not to block
+ ungetc (ch, m_input_file);
+ *c = fgetwc (m_input_file);
+ if (*c != WEOF)
+ return 1;
+#else
+ *c = ch;
+ if(*c != EOF)
+ return 1;
+#endif
+ }
+ else
+ {
+ switch (status)
{
- // We didn't strip the newlines, we just adjusted the length, and
- // we want to add the history item with the newlines
- if (m_history_sp)
- m_history_sp->Enter(line_cstr);
-
- // Copy the part of the c string that we want (removing the newline chars)
- line = std::move(line_ref.str());
+ case lldb::eConnectionStatusInterrupted:
+ m_editor_status = EditorStatus::Interrupted;
+ printf ("^C\n");
+ return 0;
+
+ case lldb::eConnectionStatusSuccess: // Success
+ break;
+
+ 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;
}
}
}
- else
- {
- error.SetErrorString("the EditLine instance has been deleted");
- }
- return error;
}
+const char *
+Editline::Prompt()
+{
+ if (m_color_prompts)
+ m_needs_prompt_repaint = true;
+ return m_current_prompt.c_str();
+}
-Error
-Editline::GetLine(std::string &line, bool &interrupted)
+unsigned char
+Editline::BreakLineCommand (int ch)
{
- Error error;
- interrupted = false;
- line.clear();
+ // 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("");
- // Set arrow key bindings for up and down arrows for single line
- // mode where up and down arrows do prev/next history
- m_interrupted = false;
+ // Establish the new cursor position at the start of a line when inserting a line break
+ m_revert_cursor_index = 0;
- if (!m_got_eof)
+ // Don't perform end of input detection or automatic formatting when pasting
+ if (!IsInputPending (m_input_file))
{
- if (m_getting_line)
+ // If this is the end of the last line, treat this as a potential exit
+ if (m_current_line_index == m_input_lines.size() - 1 && new_line_fragment.length() == 0)
{
- error.SetErrorString("already getting a line");
- return error;
+ bool end_of_input = true;
+ if (m_is_input_complete_callback)
+ {
+ SaveEditedLine();
+ auto lines = GetInputAsStringList();
+ end_of_input = m_is_input_complete_callback (this, lines, m_is_input_complete_callback_baton);
+
+ // The completion test is allowed to change the input lines when complete
+ if (end_of_input)
+ {
+ 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
+ }
+ }
+ }
+ if (end_of_input)
+ {
+ fprintf (m_output_file, "\n");
+ m_editor_status = EditorStatus::Complete;
+ return CC_NEWLINE;
+ }
}
- if (m_lines_curr_line > 0)
+
+ // Apply smart indentation
+ if (m_fix_indentation_callback)
{
- error.SetErrorString("already getting lines");
- return error;
+ 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);
}
- m_getting_line = true;
- error = PrivateGetLine(line);
- m_getting_line = false;
}
+
+ // 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;
+}
- interrupted = m_interrupted;
-
- if (m_got_eof && line.empty())
+unsigned char
+Editline::DeleteNextCharCommand (int ch)
+{
+ LineInfoW * info = (LineInfoW *)el_wline (m_editline);
+
+ // Just delete the next character normally if possible
+ if (info->cursor < info->lastchar)
{
- // Only set the error if we didn't get an error back from PrivateGetLine()
- if (error.Success())
- error.SetErrorString("end of file");
+ info->cursor++;
+ el_deletestr (m_editline, 1);
+ return CC_REFRESH;
}
- return error;
+ // 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;
}
-size_t
-Editline::Push (const char *bytes, size_t len)
+unsigned char
+Editline::DeletePreviousCharCommand (int ch)
{
- if (m_editline)
+ LineInfoW * info = (LineInfoW *)el_wline (m_editline);
+
+ // Just delete the previous character normally when not at the start of a line
+ if (info->cursor > info->buffer)
{
- // Must NULL terminate the string for el_push() so we stick it
- // into a std::string first
- ::el_push(m_editline,
- const_cast<char*>(std::string (bytes, len).c_str()));
- return len;
+ el_deletestr (m_editline, 1);
+ return CC_REFRESH;
}
- return 0;
+
+ // 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;
}
-
-Error
-Editline::GetLines(const std::string &end_line, StringList &lines, bool &interrupted)
+unsigned char
+Editline::PreviousLineCommand (int ch)
{
- Error error;
- interrupted = false;
- if (m_getting_line)
- {
- error.SetErrorString("already getting a line");
- return error;
+ SaveEditedLine();
+
+ if (m_current_line_index == 0) {
+ return RecallHistory (true);
}
- if (m_lines_curr_line > 0)
+
+ // 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())
{
- error.SetErrorString("already getting lines");
- return error;
+ m_input_lines.erase (m_input_lines.begin() + m_current_line_index);
+ fprintf (m_output_file, ANSI_CLEAR_BELOW);
}
- // Set arrow key bindings for up and down arrows for multiple line
- // mode where up and down arrows do edit prev/next line
- m_interrupted = false;
-
- LineStatus line_status = LineStatus::Success;
+ 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;
+}
- lines.Clear();
+unsigned char
+Editline::NextLineCommand (int ch)
+{
+ SaveEditedLine();
- FILE *out_file = GetOutputFile();
- FILE *err_file = GetErrorFile();
- m_lines_curr_line = 1;
- while (line_status != LineStatus::Done)
+ // Handle attempts to move down from the last line
+ if (m_current_line_index == m_input_lines.size() - 1)
{
- const uint32_t line_idx = m_lines_curr_line-1;
- if (line_idx >= lines.GetSize())
- lines.SetSize(m_lines_curr_line);
- m_lines_max_line = lines.GetSize();
- m_lines_command = Command::None;
- assert(line_idx < m_lines_max_line);
- std::string &line = lines[line_idx];
- error = PrivateGetLine(line);
- if (error.Fail())
+ // Don't add an extra line if the existing last line is blank, move through history instead
+ if (IsOnlySpaces())
{
- line_status = LineStatus::Error;
+ return RecallHistory (false);
}
- else if (m_interrupted)
+
+ // Determine indentation for the new line
+ int indentation = 0;
+ if (m_fix_indentation_callback)
{
- interrupted = true;
- line_status = LineStatus::Done;
+ StringList lines = GetInputAsStringList();
+ lines.AppendString("");
+ indentation = m_fix_indentation_callback (this, lines, 0, m_fix_indentation_callback_baton);
}
- else
- {
- switch (m_lines_command)
- {
- case Command::None:
- if (m_line_complete_callback)
- {
- line_status = m_line_complete_callback (this,
- lines,
- line_idx,
- error,
- m_line_complete_callback_baton);
- }
- else if (line == end_line)
- {
- line_status = LineStatus::Done;
- }
+ 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;
+}
- if (line_status == LineStatus::Success)
- {
- ++m_lines_curr_line;
- // If we already have content for the next line because
- // we were editing previous lines, then populate the line
- // with the appropriate contents
- if (line_idx+1 < lines.GetSize() && !lines[line_idx+1].empty())
- ::el_push (m_editline,
- const_cast<char*>(lines[line_idx+1].c_str()));
- }
- else if (line_status == LineStatus::Error)
- {
- // Clear to end of line ("ESC[K"), then print the error,
- // then go to the next line ("\n") and then move cursor up
- // two lines ("ESC[2A").
- fprintf (err_file, "\033[Kerror: %s\n\033[2A", error.AsCString());
- }
- break;
- case Command::EditPrevLine:
- if (m_lines_curr_line > 1)
- {
- //::fprintf (out_file, "\033[1A\033[%uD\033[2K", (uint32_t)(m_lines_prompt.size() + lines[line_idx].size())); // Make cursor go up a line and clear that line
- ::fprintf (out_file, "\033[1A\033[1000D\033[2K");
- if (!lines[line_idx-1].empty())
- ::el_push (m_editline,
- const_cast<char*>(lines[line_idx-1].c_str()));
- --m_lines_curr_line;
- }
- break;
- case Command::EditNextLine:
- // Allow the down arrow to create a new line
- ++m_lines_curr_line;
- //::fprintf (out_file, "\033[1B\033[%uD\033[2K", (uint32_t)(m_lines_prompt.size() + lines[line_idx].size()));
- ::fprintf (out_file, "\033[1B\033[1000D\033[2K");
- if (line_idx+1 < lines.GetSize() && !lines[line_idx+1].empty())
- ::el_push (m_editline,
- const_cast<char*>(lines[line_idx+1].c_str()));
- break;
- }
+unsigned char
+Editline::FixIndentationCommand (int ch)
+{
+ if (!m_fix_indentation_callback)
+ return CC_NORM;
+
+ // Insert the character by hand prior to correction
+ EditLineCharType inserted[] = { (EditLineCharType)ch, 0 };
+ el_winsertstr (m_editline, inserted);
+ SaveEditedLine();
+ StringList lines = GetInputAsStringList (m_current_line_index + 1);
+
+ // Determine the cursor position
+ LineInfoW * info = (LineInfoW *)el_wline (m_editline);
+ int cursor_position = info->cursor - info->buffer;
+
+ int indent_correction = m_fix_indentation_callback (this, lines, cursor_position, m_fix_indentation_callback_baton);
+
+ // Adjust the input buffer to correct indentation
+ if (indent_correction > 0)
+ {
+ info->cursor = info->buffer;
+ el_winsertstr (m_editline, EditLineStringType (indent_correction, EditLineCharType(' ')).c_str());
+ }
+ else if (indent_correction < 0)
+ {
+ info->cursor = info->buffer - indent_correction;
+ el_wdeletestr (m_editline, -indent_correction);
+ }
+ info->cursor = info->buffer + cursor_position + indent_correction;
+ return CC_REFRESH;
+}
+
+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 = (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;
}
- m_lines_curr_line = 0;
- m_lines_command = Command::None;
+ return CC_REFRESH;
+}
- // If we have a callback, call it one more time to let the
- // user know the lines are complete
- if (m_line_complete_callback && !interrupted)
- m_line_complete_callback (this,
- lines,
- UINT32_MAX,
- error,
- m_line_complete_callback_baton);
+unsigned char
+Editline::BufferStartCommand (int ch)
+{
+ SaveEditedLine();
+ MoveCursor (CursorLocation::EditingCursor, CursorLocation::BlockStart);
+ SetCurrentLine (0);
+ m_revert_cursor_index = 0;
+ return CC_NEWLINE;
+}
- return error;
+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;
}
unsigned char
-Editline::HandleCompletion (int ch)
+Editline::TabCommand (int ch)
{
- if (m_completion_callback == NULL)
+ if (m_completion_callback == nullptr)
return CC_ERROR;
-
- const LineInfo *line_info = ::el_line(m_editline);
+
+ const LineInfo *line_info = el_line (m_editline);
StringList completions;
int page_size = 40;
-
+
const int num_completions = m_completion_callback (line_info->buffer,
line_info->cursor,
line_info->lastchar,
@@ -536,25 +922,25 @@ Editline::HandleCompletion (int ch)
completions,
m_completion_callback_baton);
- FILE *out_file = GetOutputFile();
-
-// if (num_completions == -1)
-// {
-// ::el_insertstr (m_editline, m_completion_key);
-// return CC_REDISPLAY;
-// }
-// else
+ if (num_completions == 0)
+ return CC_ERROR;
+ // if (num_completions == -1)
+ // {
+ // el_insertstr (m_editline, m_completion_key);
+ // return CC_REDISPLAY;
+ // }
+ // else
if (num_completions == -2)
{
// Replace the entire line with the first string...
- ::el_deletestr (m_editline, line_info->cursor - line_info->buffer);
- ::el_insertstr (m_editline, completions.GetStringAtIndex(0));
+ el_deletestr (m_editline, line_info->cursor - line_info->buffer);
+ el_insertstr (m_editline, completions.GetStringAtIndex (0));
return CC_REDISPLAY;
}
// If we get a longer match display that first.
- const char *completion_str = completions.GetStringAtIndex(0);
- if (completion_str != NULL && *completion_str != '\0')
+ const char *completion_str = completions.GetStringAtIndex (0);
+ if (completion_str != nullptr && *completion_str != '\0')
{
el_insertstr (m_editline, completion_str);
return CC_REDISPLAY;
@@ -563,15 +949,15 @@ Editline::HandleCompletion (int ch)
if (num_completions > 1)
{
int num_elements = num_completions + 1;
- ::fprintf (out_file, "\nAvailable completions:");
+ fprintf (m_output_file, "\n" ANSI_CLEAR_BELOW "Available completions:");
if (num_completions < page_size)
{
for (int i = 1; i < num_elements; i++)
{
- completion_str = completions.GetStringAtIndex(i);
- ::fprintf (out_file, "\n\t%s", completion_str);
+ completion_str = completions.GetStringAtIndex (i);
+ fprintf (m_output_file, "\n\t%s", completion_str);
}
- ::fprintf (out_file, "\n");
+ fprintf (m_output_file, "\n");
}
else
{
@@ -585,17 +971,17 @@ Editline::HandleCompletion (int ch)
endpoint = num_elements;
for (; cur_pos < endpoint; cur_pos++)
{
- completion_str = completions.GetStringAtIndex(cur_pos);
- ::fprintf (out_file, "\n\t%s", completion_str);
+ completion_str = completions.GetStringAtIndex (cur_pos);
+ fprintf (m_output_file, "\n\t%s", completion_str);
}
if (cur_pos >= num_elements)
{
- ::fprintf (out_file, "\n");
+ fprintf (m_output_file, "\n");
break;
}
- ::fprintf (out_file, "\nMore (Y/n/a): ");
+ fprintf (m_output_file, "\nMore (Y/n/a): ");
reply = 'n';
got_char = el_getc(m_editline, &reply);
if (got_char == -1 || reply == 'n')
@@ -604,247 +990,388 @@ Editline::HandleCompletion (int ch)
page_size = num_elements - cur_pos;
}
}
-
+ DisplayInput();
+ MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);
}
-
- if (num_completions == 0)
- return CC_REFRESH_BEEP;
- else
- return CC_REDISPLAY;
+ return CC_REDISPLAY;
}
-Editline *
-Editline::GetClientData (::EditLine *e)
+void
+Editline::ConfigureEditor (bool multiline)
{
- Editline *editline = NULL;
- if (e && ::el_get(e, EL_CLIENTDATA, &editline) == 0)
- return editline;
- return NULL;
-}
+ 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())
+ {
+ m_history_sp->Load();
+ 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();
+ }));
-FILE *
-Editline::GetInputFile ()
-{
- return GetFilePointer (m_editline, 0);
+ el_wset (m_editline, EL_GETCFN,
+ (EditlineGetCharCallbackType)([] (EditLine * editline, EditLineCharType * c) {
+ return Editline::InstanceFor (editline)->GetCharacter (c);
+ }));
+
+ // Commands used for multiline support, registered whether or not they're used
+ el_set (m_editline, EL_ADDFN, "lldb-break-line", "Insert a line break",
+ (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
+ return Editline::InstanceFor (editline)->BreakLineCommand (ch);
+ }));
+ el_set (m_editline, EL_ADDFN, "lldb-delete-next-char", "Delete next character",
+ (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
+ return Editline::InstanceFor (editline)->DeleteNextCharCommand (ch);
+ }));
+ el_set (m_editline, EL_ADDFN, "lldb-delete-previous-char", "Delete previous character",
+ (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
+ return Editline::InstanceFor (editline)->DeletePreviousCharCommand (ch);
+ }));
+ el_set (m_editline, EL_ADDFN, "lldb-previous-line", "Move to previous line",
+ (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
+ return Editline::InstanceFor (editline)->PreviousLineCommand (ch);
+ }));
+ el_set (m_editline, EL_ADDFN, "lldb-next-line", "Move to next line",
+ (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
+ return Editline::InstanceFor (editline)->NextLineCommand (ch);
+ }));
+ el_set (m_editline, EL_ADDFN, "lldb-buffer-start", "Move to start of buffer",
+ (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
+ return Editline::InstanceFor (editline)->BufferStartCommand (ch);
+ }));
+ el_set (m_editline, EL_ADDFN, "lldb-buffer-end", "Move to end of buffer",
+ (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
+ return Editline::InstanceFor (editline)->BufferEndCommand (ch);
+ }));
+ el_set (m_editline, EL_ADDFN, "lldb-fix-indentation", "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 becuase 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_set (m_editline, EL_ADDFN, "lldb-complete", "Invoke completion", complete_callback);
+ el_set (m_editline, EL_ADDFN, "lldb_complete", "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 user-specific customization prior to registering bindings we absolutely require
+ el_source (m_editline, NULL);
+
+ // Register an internal binding that external developers shouldn't use
+ el_set (m_editline, EL_ADDFN, "lldb-revert-line", "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-break-line", NULL);
+ el_set (m_editline, EL_BIND, "\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);
+ }
+ 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);
+ }
+ }
}
-FILE *
-Editline::GetOutputFile ()
+//------------------------------------------------------------------
+// Editline public methods
+//------------------------------------------------------------------
+
+Editline *
+Editline::InstanceFor (EditLine * editline)
{
- return GetFilePointer (m_editline, 1);
+ Editline * editor;
+ el_get (editline, EL_CLIENTDATA, &editor);
+ return editor;
}
-FILE *
-Editline::GetErrorFile ()
+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)
{
- return GetFilePointer (m_editline, 2);
+ // Get a shared history instance
+ m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name;
+ m_history_sp = EditlineHistory::GetHistory (m_editor_name);
}
-const char *
-Editline::GetPrompt()
+Editline::~Editline()
{
- if (m_prompt_with_line_numbers && m_lines_curr_line > 0)
+ if (m_editline)
{
- StreamString strm;
- strm.Printf("%3u: ", m_lines_curr_line);
- m_lines_prompt = std::move(strm.GetString());
- return m_lines_prompt.c_str();
- }
- else
- {
- return m_prompt.c_str();
+ // 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 *p)
+Editline::SetPrompt (const char * prompt)
{
- if (p && p[0])
- m_prompt = p;
- else
- m_prompt.clear();
- size_t start_pos = 0;
- size_t escape_pos;
- while ((escape_pos = m_prompt.find('\033', start_pos)) != std::string::npos)
- {
- m_prompt.insert(escape_pos, 1, k_prompt_escape_char);
- start_pos += 2;
- }
+ m_set_prompt = prompt == nullptr ? "" : prompt;
}
-FILE *
-Editline::GetFilePointer (::EditLine *e, int fd)
+void
+Editline::SetContinuationPrompt (const char * continuation_prompt)
{
- FILE *file_ptr = NULL;
- if (e && ::el_get(e, EL_GETFP, fd, &file_ptr) == 0)
- return file_ptr;
- return NULL;
+ m_set_continuation_prompt = continuation_prompt == nullptr ? "" : continuation_prompt;
}
-unsigned char
-Editline::CallbackEditPrevLine (::EditLine *e, int ch)
+void
+Editline::TerminalSizeChanged()
{
- Editline *editline = GetClientData (e);
- if (editline->m_lines_curr_line > 1)
+ if (m_editline != nullptr)
{
- editline->m_lines_command = Command::EditPrevLine;
- return CC_NEWLINE;
+ el_resize (m_editline);
+ int columns;
+ // Despite the man page claiming non-zero indicates success, it's actually zero
+ if (el_get (m_editline, EL_GETTC, "co", &columns) == 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;
+ }
}
- return CC_ERROR;
}
-unsigned char
-Editline::CallbackEditNextLine (::EditLine *e, int ch)
+
+const char *
+Editline::GetPrompt()
{
- Editline *editline = GetClientData (e);
- if (editline->m_lines_curr_line < editline->m_lines_max_line)
- {
- editline->m_lines_command = Command::EditNextLine;
- return CC_NEWLINE;
- }
- return CC_ERROR;
+ return m_set_prompt.c_str();
}
-unsigned char
-Editline::CallbackComplete (::EditLine *e, int ch)
+uint32_t
+Editline::GetCurrentLine()
{
- Editline *editline = GetClientData (e);
- if (editline)
- return editline->HandleCompletion (ch);
- return CC_ERROR;
+ return m_current_line_index;
}
-const char *
-Editline::GetPromptCallback (::EditLine *e)
+void
+Editline::Hide()
{
- Editline *editline = GetClientData (e);
- if (editline)
- return editline->GetPrompt();
- return "";
+ // Make sure we're at a stable location waiting for input
+ while (m_editor_status == EditorStatus::Editing && !m_editor_getting_char)
+ {
+ usleep(100000);
+ }
+
+ // Clear the existing input
+ if (m_editor_status == EditorStatus::Editing)
+ {
+ MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);
+ fprintf(m_output_file, ANSI_CLEAR_BELOW);
+ }
}
-int
-Editline::GetCharFromInputFileCallback (EditLine *e, char *c)
+void
+Editline::Refresh()
{
- Editline *editline = GetClientData (e);
- if (editline && editline->m_got_eof == false)
+ if (m_editor_status == EditorStatus::Editing)
{
- FILE *f = editline->GetInputFile();
- if (f == NULL)
- {
- editline->m_got_eof = true;
- return 0;
- }
-
-
- while (1)
- {
- lldb::ConnectionStatus status = eConnectionStatusSuccess;
- char ch = 0;
- // When we start to call el_gets() the editline library needs to
- // output the prompt
- editline->m_getting_char.SetValue(true, eBroadcastAlways);
- const size_t n = editline->m_file.Read(&ch, 1, UINT32_MAX, status, NULL);
- editline->m_getting_char.SetValue(false, eBroadcastAlways);
- if (n)
- {
- if (ch == '\x04')
- {
- // Only turn a CTRL+D into a EOF if we receive the
- // CTRL+D an empty line, otherwise it will forward
- // delete the character at the cursor
- const LineInfo *line_info = ::el_line(e);
- if (line_info != NULL &&
- line_info->buffer == line_info->cursor &&
- line_info->cursor == line_info->lastchar)
- {
- editline->m_got_eof = true;
- break;
- }
- }
-
- if (status == eConnectionStatusEndOfFile)
- {
- editline->m_got_eof = true;
- break;
- }
- else
- {
- *c = ch;
- return 1;
- }
- }
- else
- {
- switch (status)
- {
- case eConnectionStatusInterrupted:
- editline->m_interrupted = true;
- *c = '\n';
- return 1;
-
- case eConnectionStatusSuccess: // Success
- break;
-
- case eConnectionStatusError: // Check GetError() for details
- case eConnectionStatusTimedOut: // Request timed out
- case eConnectionStatusEndOfFile: // End-of-file encountered
- case eConnectionStatusNoConnection: // No connection
- case eConnectionStatusLostConnection: // Lost connection while connected to a valid connection
- editline->m_got_eof = true;
- break;
- }
- }
- }
+ DisplayInput();
+ MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);
}
- return 0;
}
-void
-Editline::Hide ()
+bool
+Editline::Interrupt()
{
- if (m_getting_line)
+ if (m_editor_status == EditorStatus::Editing)
{
- // If we are getting a line, we might have started to call el_gets() and
- // it might be printing the prompt. Here we make sure we are actually getting
- // a character. This way we know the entire prompt has been printed.
- TimeValue timeout = TimeValue::Now();
- timeout.OffsetWithSeconds(1);
- if (m_getting_char.WaitForValueEqualTo(true, &timeout))
- {
- FILE *out_file = GetOutputFile();
- if (out_file)
- {
- const LineInfo *line_info = ::el_line(m_editline);
- if (line_info)
- ::fprintf (out_file, "\033[%uD\033[K", (uint32_t)(strlen(GetPrompt()) + line_info->cursor - line_info->buffer));
- }
- }
+ return m_input_connection.InterruptRead();
}
+ return false; // Interrupt not handled as we weren't getting a line or lines
}
+void
+Editline::SetAutoCompleteCallback (CompleteCallbackType callback, void * baton)
+{
+ m_completion_callback = callback;
+ m_completion_callback_baton = baton;
+}
void
-Editline::Refresh()
+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)
{
- if (m_getting_line)
+ 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(""));
+
+ SetCurrentLine (0);
+ m_in_history = false;
+ m_editor_status = EditorStatus::Editing;
+ m_editor_getting_char = false;
+ m_revert_cursor_index = -1;
+
+ int count;
+ auto input = el_wgets (m_editline, &count);
+
+ interrupted = m_editor_status == EditorStatus::Interrupted;
+ if (!interrupted)
{
- // If we are getting a line, we might have started to call el_gets() and
- // it might be printing the prompt. Here we make sure we are actually getting
- // a character. This way we know the entire prompt has been printed.
- TimeValue timeout = TimeValue::Now();
- timeout.OffsetWithSeconds(1);
- if (m_getting_char.WaitForValueEqualTo(true, &timeout))
+ if (input == nullptr)
{
- ::el_set (m_editline, EL_REFRESH);
+ 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::Interrupt ()
+Editline::GetLines (int first_line_number, StringList &lines, bool &interrupted)
{
- m_interrupted = true;
- if (m_getting_line || m_lines_curr_line > 0)
- return m_file.InterruptRead();
- return false; // Interrupt not handled as we weren't getting a line or lines
+ 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(""));
+
+ // Begin the line editing loop
+ DisplayInput();
+ SetCurrentLine (0);
+ MoveCursor (CursorLocation::BlockEnd, CursorLocation::BlockStart);
+ m_editor_status = EditorStatus::Editing;
+ m_editor_getting_char = false;
+ 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;
}