//===-- Editline.cpp --------------------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "lldb/Host/Editline.h" #include "lldb/Core/Error.h" #include "lldb/Core/StreamString.h" #include "lldb/Core/StringList.h" #include "lldb/Host/Host.h" #include using namespace lldb; using namespace lldb_private; namespace lldb_private { typedef std::weak_ptr 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 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_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); } else { m_editline = ::el_init("lldb-tmp", fin, fout, ferr); } if (prompt && prompt[0]) SetPrompt (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 assert (m_editline); ::el_set (m_editline, EL_CLIENTDATA, this); // 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()) { ::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 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); // 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(); } Editline::~Editline() { // 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. ::el_set (m_editline, EL_EDITMODE, 0); ::el_end(m_editline); m_editline = NULL; } void Editline::SetGetCharCallback (GetCharCallbackType callback) { ::el_set (m_editline, EL_GETCFN, callback); } bool Editline::LoadHistory () { if (m_history_sp) return m_history_sp->Load(); return false; } bool Editline::SaveHistory () { if (m_history_sp) return m_history_sp->Save(); return false; } Error Editline::PrivateGetLine(std::string &line) { Error error; if (m_interrupted) { error.SetErrorString("interrupted"); return error; } line.clear(); if (m_editline != NULL) { 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) { error.SetError(save_errno, eErrorTypePOSIX); } else if (line_cstr) { // 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) { // 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()); } } } else { error.SetErrorString("the EditLine instance has been deleted"); } return error; } Error 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 m_interrupted = false; if (!m_got_eof) { if (m_getting_line) { error.SetErrorString("already getting a line"); return error; } if (m_lines_curr_line > 0) { error.SetErrorString("already getting lines"); return error; } m_getting_line = true; error = PrivateGetLine(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() if (error.Success()) error.SetErrorString("end of file"); } return error; } size_t Editline::Push (const char *bytes, size_t len) { if (m_editline) { // Must NULL terminate the string for el_push() so we stick it // into a std::string first ::el_push(m_editline, const_cast(std::string (bytes, len).c_str())); return len; } return 0; } Error 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"); return error; } if (m_lines_curr_line > 0) { error.SetErrorString("already getting lines"); return error; } // 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; lines.Clear(); FILE *out_file = GetOutputFile(); FILE *err_file = GetErrorFile(); m_lines_curr_line = 1; while (line_status != LineStatus::Done) { 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()) { line_status = LineStatus::Error; } else if (m_interrupted) { interrupted = true; line_status = LineStatus::Done; } 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; } 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(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(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(lines[line_idx+1].c_str())); break; } } } m_lines_curr_line = 0; m_lines_command = Command::None; // 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); return error; } unsigned char Editline::HandleCompletion (int ch) { if (m_completion_callback == NULL) return CC_ERROR; 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, 0, // Don't skip any matches (start at match zero) -1, // Get all the matches 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 == -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)); 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') { el_insertstr (m_editline, completion_str); return CC_REDISPLAY; } if (num_completions > 1) { int num_elements = num_completions + 1; ::fprintf (out_file, "\nAvailable 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); } ::fprintf (out_file, "\n"); } else { int cur_pos = 1; char reply; int got_char; while (cur_pos < num_elements) { int endpoint = cur_pos + page_size; if (endpoint > num_elements) endpoint = num_elements; for (; cur_pos < endpoint; cur_pos++) { completion_str = completions.GetStringAtIndex(cur_pos); ::fprintf (out_file, "\n\t%s", completion_str); } if (cur_pos >= num_elements) { ::fprintf (out_file, "\n"); break; } ::fprintf (out_file, "\nMore (Y/n/a): "); reply = 'n'; got_char = el_getc(m_editline, &reply); if (got_char == -1 || reply == 'n') break; if (reply == 'a') page_size = num_elements - cur_pos; } } } if (num_completions == 0) return CC_REFRESH_BEEP; else return CC_REDISPLAY; } Editline * Editline::GetClientData (::EditLine *e) { Editline *editline = NULL; if (e && ::el_get(e, EL_CLIENTDATA, &editline) == 0) return editline; return NULL; } FILE * Editline::GetInputFile () { return GetFilePointer (m_editline, 0); } FILE * Editline::GetOutputFile () { return GetFilePointer (m_editline, 1); } FILE * Editline::GetErrorFile () { return GetFilePointer (m_editline, 2); } const char * Editline::GetPrompt() { if (m_prompt_with_line_numbers && m_lines_curr_line > 0) { 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(); } } void Editline::SetPrompt (const char *p) { 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; } } FILE * Editline::GetFilePointer (::EditLine *e, int fd) { FILE *file_ptr = NULL; if (e && ::el_get(e, EL_GETFP, fd, &file_ptr) == 0) return file_ptr; return NULL; } unsigned char Editline::CallbackEditPrevLine (::EditLine *e, int ch) { Editline *editline = GetClientData (e); if (editline->m_lines_curr_line > 1) { editline->m_lines_command = Command::EditPrevLine; return CC_NEWLINE; } return CC_ERROR; } unsigned char Editline::CallbackEditNextLine (::EditLine *e, int ch) { 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; } unsigned char Editline::CallbackComplete (::EditLine *e, int ch) { Editline *editline = GetClientData (e); if (editline) return editline->HandleCompletion (ch); return CC_ERROR; } const char * Editline::GetPromptCallback (::EditLine *e) { Editline *editline = GetClientData (e); if (editline) return editline->GetPrompt(); return ""; } int Editline::GetCharFromInputFileCallback (EditLine *e, char *c) { Editline *editline = GetClientData (e); if (editline && editline->m_got_eof == false) { 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; } } } } return 0; } void Editline::Hide () { 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)) { 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)); } } } } void Editline::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); } } } bool Editline::Interrupt () { 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 }