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.cpp418
1 files changed, 286 insertions, 132 deletions
diff --git a/source/Host/common/Editline.cpp b/source/Host/common/Editline.cpp
index 679aadd54c4f..7af9f39a7863 100644
--- a/source/Host/common/Editline.cpp
+++ b/source/Host/common/Editline.cpp
@@ -20,30 +20,158 @@
using namespace lldb;
using namespace lldb_private;
+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 ()
+ {
+ m_history = ::history_init();
+ ::history (m_history, &m_event, H_SETSIZE, size);
+ if (unique_entries)
+ ::history (m_history, &m_event, H_SETUNIQUE, 1);
+ }
+
+ const char *
+ GetHistoryFilePath()
+ {
+ if (m_path.empty() && m_history && !m_prefix.empty())
+ {
+ 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());
+ }
+ if (m_path.empty())
+ return NULL;
+ return m_path.c_str();
+ }
+
+ public:
+
+ ~EditlineHistory()
+ {
+ Save ();
+
+ if (m_history)
+ {
+ ::history_end (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())
+ {
+ 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 != 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)
+ {
+ const char *path = GetHistoryFilePath();
+ if (path)
+ {
+ ::history (m_history, &m_event, H_LOAD, path);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool
+ Save ()
+ {
+ if (m_history)
+ {
+ const char *path = GetHistoryFilePath();
+ if (path)
+ {
+ ::history (m_history, &m_event, H_SAVE, path);
+ return true;
+ }
+ }
+ 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
+ };
+}
+
+
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 (NULL),
- m_history_event (),
- m_program (),
+ m_history_sp (),
m_prompt (),
m_lines_prompt (),
- m_getc_buffer (),
- m_getc_mutex (Mutex::eMutexTypeNormal),
- m_getc_cond (),
-// m_gets_mutex (Mutex::eMutexTypeNormal),
+ 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),
@@ -51,14 +179,16 @@ Editline::Editline (const char *prog, // prog can't be NULL
{
if (prog && prog[0])
{
- m_program = prog;
m_editline = ::el_init(prog, fin, fout, ferr);
- m_history = ::history_init();
+
+ // Get a shared history instance
+ m_history_sp = EditlineHistory::GetHistory(prog);
}
else
{
m_editline = ::el_init("lldb-tmp", fin, fout, ferr);
}
+
if (prompt && prompt[0])
SetPrompt (prompt);
@@ -76,28 +206,44 @@ Editline::Editline (const char *prog, // prog can't be NULL
::el_set (m_editline, EL_PROMPT, GetPromptCallback);
#endif
::el_set (m_editline, EL_EDITOR, "emacs");
- if (m_history)
+ if (m_history_sp && m_history_sp->IsValid())
{
- ::el_set (m_editline, EL_HIST, history, m_history);
+ ::el_set (m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr());
}
::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);
::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 autocompelte
+ ::el_set (m_editline, EL_BIND, "\t", "lldb-complete", NULL); // Bind TAB to be auto complete
+
+ if (configure_for_multiline)
+ {
+ // 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
+ }
+ else
+ {
+ // 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
+ }
// Source $PWD/.editrc then $HOME/.editrc
::el_source (m_editline, NULL);
- if (m_history)
- {
- ::history (m_history, &m_history_event, H_SETSIZE, 800);
- ::history (m_history, &m_history_event, H_SETUNIQUE, 1);
- }
-
// 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
@@ -109,14 +255,12 @@ Editline::Editline (const char *prog, // prog can't be NULL
Editline::~Editline()
{
- SaveHistory();
-
- if (m_history)
- {
- ::history_end (m_history);
- m_history = NULL;
- }
-
+ // 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();
+
// 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.
@@ -132,36 +276,19 @@ Editline::SetGetCharCallback (GetCharCallbackType callback)
::el_set (m_editline, EL_GETCFN, callback);
}
-FileSpec
-Editline::GetHistoryFile()
-{
- char history_path[PATH_MAX];
- ::snprintf (history_path, sizeof(history_path), "~/.%s-history", m_program.c_str());
- return FileSpec(history_path, true);
-}
-
bool
Editline::LoadHistory ()
{
- if (m_history)
- {
- FileSpec history_file(GetHistoryFile());
- if (history_file.Exists())
- ::history (m_history, &m_history_event, H_LOAD, history_file.GetPath().c_str());
- return true;
- }
+ if (m_history_sp)
+ return m_history_sp->Load();
return false;
}
bool
Editline::SaveHistory ()
{
- if (m_history)
- {
- std::string history_path = GetHistoryFile().GetPath();
- ::history (m_history, &m_history_event, H_SAVE, history_path.c_str());
- return true;
- }
+ if (m_history_sp)
+ return m_history_sp->Save();
return false;
}
@@ -180,13 +307,8 @@ Editline::PrivateGetLine(std::string &line)
if (m_editline != NULL)
{
int line_len = 0;
- const char *line_cstr = NULL;
// Call el_gets to prompt the user and read the user's input.
-// {
-// // Make sure we know when we are in el_gets() by using a mutex
-// Mutex::Locker locker (m_gets_mutex);
- line_cstr = ::el_gets (m_editline, &line_len);
-// }
+ const char *line_cstr = ::el_gets (m_editline, &line_len);
static int save_errno = (line_len < 0) ? errno : 0;
@@ -198,20 +320,18 @@ Editline::PrivateGetLine(std::string &line)
{
// Decrement the length so we don't have newline characters in "line" for when
// we assign the cstr into the std::string
- while (line_len > 0 &&
- (line_cstr[line_len - 1] == '\n' ||
- line_cstr[line_len - 1] == '\r'))
- --line_len;
+ llvm::StringRef line_ref (line_cstr);
+ line_ref = line_ref.rtrim("\n\r");
- if (line_len > 0)
+ if (!line_ref.empty() && !m_interrupted)
{
// 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)
- ::history (m_history, &m_history_event, H_ENTER, line_cstr);
+ 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.assign(line_cstr, line_len);
+ line = std::move(line_ref.str());
}
}
}
@@ -224,15 +344,14 @@ Editline::PrivateGetLine(std::string &line)
Error
-Editline::GetLine(std::string &line)
+Editline::GetLine(std::string &line, bool &interrupted)
{
Error error;
+ interrupted = false;
line.clear();
// Set arrow key bindings for up and down arrows for single line
// mode where up and down arrows do prev/next history
- ::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
m_interrupted = false;
if (!m_got_eof)
@@ -252,6 +371,8 @@ Editline::GetLine(std::string &line)
m_getting_line = false;
}
+ interrupted = m_interrupted;
+
if (m_got_eof && line.empty())
{
// Only set the error if we didn't get an error back from PrivateGetLine()
@@ -278,9 +399,10 @@ Editline::Push (const char *bytes, size_t len)
Error
-Editline::GetLines(const std::string &end_line, StringList &lines)
+Editline::GetLines(const std::string &end_line, StringList &lines, bool &interrupted)
{
Error error;
+ interrupted = false;
if (m_getting_line)
{
error.SetErrorString("already getting a line");
@@ -294,10 +416,6 @@ Editline::GetLines(const std::string &end_line, StringList &lines)
// Set arrow key bindings for up and down arrows for multiple line
// mode where up and down arrows do edit prev/next line
- ::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
- ::el_set (m_editline, EL_BIND, "^b", "ed-prev-history", NULL);
- ::el_set (m_editline, EL_BIND, "^n", "ed-next-history", NULL);
m_interrupted = false;
LineStatus line_status = LineStatus::Success;
@@ -321,6 +439,11 @@ Editline::GetLines(const std::string &end_line, StringList &lines)
{
line_status = LineStatus::Error;
}
+ else if (m_interrupted)
+ {
+ interrupted = true;
+ line_status = LineStatus::Done;
+ }
else
{
switch (m_lines_command)
@@ -385,7 +508,7 @@ Editline::GetLines(const std::string &end_line, StringList &lines)
// If we have a callback, call it one more time to let the
// user know the lines are complete
- if (m_line_complete_callback)
+ if (m_line_complete_callback && !interrupted)
m_line_complete_callback (this,
lines,
UINT32_MAX,
@@ -599,70 +722,78 @@ Editline::GetPromptCallback (::EditLine *e)
return "";
}
-size_t
-Editline::SetInputBuffer (const char *c, size_t len)
-{
- if (c && len > 0)
- {
- Mutex::Locker locker(m_getc_mutex);
- SetGetCharCallback(GetCharInputBufferCallback);
- m_getc_buffer.append(c, len);
- m_getc_cond.Broadcast();
- }
- return len;
-}
-
-int
-Editline::GetChar (char *c)
-{
- Mutex::Locker locker(m_getc_mutex);
- if (m_getc_buffer.empty())
- m_getc_cond.Wait(m_getc_mutex);
- if (m_getc_buffer.empty())
- return 0;
- *c = m_getc_buffer[0];
- m_getc_buffer.erase(0,1);
- return 1;
-}
-
-int
-Editline::GetCharInputBufferCallback (EditLine *e, char *c)
-{
- Editline *editline = GetClientData (e);
- if (editline)
- return editline->GetChar(c);
- return 0;
-}
-
int
Editline::GetCharFromInputFileCallback (EditLine *e, char *c)
{
Editline *editline = GetClientData (e);
if (editline && editline->m_got_eof == false)
{
- char ch = ::fgetc(editline->GetInputFile());
- 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)
- {
- ch = EOF;
- }
- }
-
- if (ch == EOF)
+ FILE *f = editline->GetInputFile();
+ if (f == NULL)
{
editline->m_got_eof = true;
+ return 0;
}
- else
+
+
+ while (1)
{
- *c = ch;
- return 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;
+ }
+ }
}
}
return 0;
@@ -671,12 +802,23 @@ Editline::GetCharFromInputFileCallback (EditLine *e, char *c)
void
Editline::Hide ()
{
- FILE *out_file = GetOutputFile();
- if (out_file)
+ if (m_getting_line)
{
- 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));
+ // 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));
+ }
+ }
}
}
@@ -684,13 +826,25 @@ Editline::Hide ()
void
Editline::Refresh()
{
- ::el_set (m_editline, EL_REFRESH);
+ if (m_getting_line)
+ {
+ // 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))
+ {
+ ::el_set (m_editline, EL_REFRESH);
+ }
+ }
}
-void
+bool
Editline::Interrupt ()
{
m_interrupted = true;
if (m_getting_line || m_lines_curr_line > 0)
- el_insertstr(m_editline, "\n"); // True to force the line to complete itself so we get exit from el_gets()
+ return m_file.InterruptRead();
+ return false; // Interrupt not handled as we weren't getting a line or lines
}