diff options
Diffstat (limited to 'source/Host/common/Editline.cpp')
-rw-r--r-- | source/Host/common/Editline.cpp | 418 |
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 } |