//===-- IOChannel.cpp -------------------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "IOChannel.h" #include #include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBDebugger.h" #include "lldb/API/SBError.h" #include "lldb/API/SBEvent.h" #include "lldb/API/SBFileSpec.h" #include "lldb/API/SBHostOS.h" #include "lldb/API/SBListener.h" #include "lldb/API/SBStringList.h" #include #include using namespace lldb; typedef std::map PromptMap; const char *g_default_prompt = "(lldb) "; PromptMap g_prompt_map; // Printing the following string causes libedit to back up to the beginning of the line & blank it out. const char undo_prompt_string[4] = { (char) 13, (char) 27, (char) 91, (char) 75}; static const char* el_prompt(EditLine *el) { PromptMap::const_iterator pos = g_prompt_map.find (el); if (pos == g_prompt_map.end()) return g_default_prompt; return pos->second.c_str(); } const char * IOChannel::GetPrompt () { PromptMap::const_iterator pos = g_prompt_map.find (m_edit_line); if (pos == g_prompt_map.end()) return g_default_prompt; return pos->second.c_str(); } bool IOChannel::EditLineHasCharacters () { const LineInfo *line_info = el_line(m_edit_line); if (line_info) { // Sometimes we get called after the user has submitted the line, but before editline has // cleared the buffer. In that case the cursor will be pointing at the newline. That's // equivalent to having no characters on the line, since it has already been submitted. if (*line_info->cursor == '\n') return false; else return line_info->cursor != line_info->buffer; } else return false; } void IOChannel::EraseCharsBeforeCursor () { const LineInfo *line_info = el_line(m_edit_line); el_deletestr(m_edit_line, line_info->cursor - line_info->buffer); } unsigned char IOChannel::ElCompletionFn (EditLine *e, int ch) { IOChannel *io_channel; if (el_get(e, EL_CLIENTDATA, &io_channel) == 0) { return io_channel->HandleCompletion (e, ch); } else { return CC_ERROR; } } void IOChannel::ElResize() { el_resize(m_edit_line); } unsigned char IOChannel::HandleCompletion (EditLine *e, int ch) { assert (e == m_edit_line); const LineInfo *line_info = el_line(m_edit_line); SBStringList completions; int page_size = 40; int num_completions = m_driver->GetDebugger().GetCommandInterpreter().HandleCompletion (line_info->buffer, line_info->cursor, line_info->lastchar, 0, -1, completions); if (num_completions == -1) { el_insertstr (m_edit_line, m_completion_key); return CC_REDISPLAY; } else if (num_completions == -2) { el_deletestr (m_edit_line, line_info->cursor - line_info->buffer); el_insertstr (m_edit_line, 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_edit_line, completion_str); return CC_REDISPLAY; } if (num_completions > 1) { const char *comment = "\nAvailable completions:"; int num_elements = num_completions + 1; OutWrite(comment, strlen (comment), NO_ASYNC); if (num_completions < page_size) { for (int i = 1; i < num_elements; i++) { completion_str = completions.GetStringAtIndex(i); OutWrite("\n\t", 2, NO_ASYNC); OutWrite(completion_str, strlen (completion_str), NO_ASYNC); } OutWrite ("\n", 1, NO_ASYNC); } 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); OutWrite("\n\t", 2, NO_ASYNC); OutWrite(completion_str, strlen (completion_str), NO_ASYNC); } if (cur_pos >= num_elements) { OutWrite("\n", 1, NO_ASYNC); break; } OutWrite("\nMore (Y/n/a): ", strlen ("\nMore (Y/n/a): "), NO_ASYNC); reply = 'n'; got_char = el_getc(m_edit_line, &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; } IOChannel::IOChannel ( FILE *editline_in, FILE *editline_out, FILE *out, FILE *err, Driver *driver ) : SBBroadcaster ("IOChannel"), m_output_mutex (), m_enter_elgets_time (), m_driver (driver), m_read_thread (LLDB_INVALID_HOST_THREAD), m_read_thread_should_exit (false), m_out_file (out), m_err_file (err), m_command_queue (), m_completion_key ("\t"), m_edit_line (::el_init (SBHostOS::GetProgramFileSpec().GetFilename(), editline_in, editline_out, editline_out)), m_history (history_init()), m_history_event(), m_getting_command (false), m_expecting_prompt (false), m_prompt_str (), m_refresh_request_pending (false) { assert (m_edit_line); ::el_set (m_edit_line, EL_PROMPT, el_prompt); ::el_set (m_edit_line, EL_EDITOR, "emacs"); ::el_set (m_edit_line, EL_HIST, history, m_history); el_set (m_edit_line, EL_ADDFN, "lldb_complete", "LLDB completion function", IOChannel::ElCompletionFn); el_set (m_edit_line, EL_BIND, m_completion_key, "lldb_complete", NULL); el_set (m_edit_line, EL_BIND, "^r", "em-inc-search-prev", NULL); // Cycle through backwards search, entering string el_set (m_edit_line, EL_BIND, "^w", "ed-delete-prev-word", NULL); // Delete previous word, behave like bash does. el_set (m_edit_line, EL_BIND, "\e[3~", "ed-delete-next-char", NULL); // Fix the delete key. el_set (m_edit_line, EL_CLIENTDATA, this); // Source $PWD/.editrc then $HOME/.editrc ::el_source (m_edit_line, NULL); assert (m_history); ::history (m_history, &m_history_event, H_SETSIZE, 800); ::history (m_history, &m_history_event, H_SETUNIQUE, 1); // Load history HistorySaveLoad (false); // Set up mutex to make sure OutErr, OutWrite and RefreshPrompt do not interfere // with each other when writing. int error; ::pthread_mutexattr_t attr; error = ::pthread_mutexattr_init (&attr); assert (error == 0); error = ::pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE); assert (error == 0); error = ::pthread_mutex_init (&m_output_mutex, &attr); assert (error == 0); error = ::pthread_mutexattr_destroy (&attr); assert (error == 0); // Initialize time that ::el_gets was last called. m_enter_elgets_time.tv_sec = 0; m_enter_elgets_time.tv_usec = 0; } IOChannel::~IOChannel () { // Save history HistorySaveLoad (true); if (m_history != NULL) { ::history_end (m_history); m_history = NULL; } if (m_edit_line != NULL) { ::el_end (m_edit_line); m_edit_line = NULL; } ::pthread_mutex_destroy (&m_output_mutex); } void IOChannel::HistorySaveLoad (bool save) { if (m_history != NULL) { char history_path[PATH_MAX]; ::snprintf (history_path, sizeof(history_path), "~/.%s-history", SBHostOS::GetProgramFileSpec().GetFilename()); if ((size_t)SBFileSpec::ResolvePath (history_path, history_path, sizeof(history_path)) < sizeof(history_path) - 1) { const char *path_ptr = history_path; if (save) ::history (m_history, &m_history_event, H_SAVE, path_ptr); else ::history (m_history, &m_history_event, H_LOAD, path_ptr); } } } void IOChannel::LibeditOutputBytesReceived (void *baton, const void *src, size_t src_len) { // Make this a member variable. // static std::string prompt_str; IOChannel *io_channel = (IOChannel *) baton; IOLocker locker (io_channel->m_output_mutex); const char *bytes = (const char *) src; if (io_channel->IsGettingCommand() && io_channel->m_expecting_prompt) { io_channel->m_prompt_str.append (bytes, src_len); // Log this to make sure the prompt is really what you think it is. if (io_channel->m_prompt_str.find (el_prompt(io_channel->m_edit_line)) == 0) { io_channel->m_expecting_prompt = false; io_channel->m_refresh_request_pending = false; io_channel->OutWrite (io_channel->m_prompt_str.c_str(), io_channel->m_prompt_str.size(), NO_ASYNC); io_channel->m_prompt_str.clear(); } } else { if (io_channel->m_prompt_str.size() > 0) io_channel->m_prompt_str.clear(); std::string tmp_str (bytes, src_len); if (tmp_str.find (el_prompt (io_channel->m_edit_line)) == 0) io_channel->m_refresh_request_pending = false; io_channel->OutWrite (bytes, src_len, NO_ASYNC); } } IOChannel::LibeditGetInputResult IOChannel::LibeditGetInput (std::string &new_line) { IOChannel::LibeditGetInputResult retval = IOChannel::eLibeditGetInputResultUnknown; if (m_edit_line != NULL) { int line_len = 0; // Set boolean indicating whether or not el_gets is trying to get input (i.e. whether or not to attempt // to refresh the prompt after writing data). SetGettingCommand (true); m_expecting_prompt = true; // Call el_gets to prompt the user and read the user's input. const char *line = ::el_gets (m_edit_line, &line_len); // Re-set the boolean indicating whether or not el_gets is trying to get input. SetGettingCommand (false); if (line) { retval = IOChannel::eLibeditGetInputValid; // strip any newlines off the end of the string... while (line_len > 0 && (line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) --line_len; if (line_len > 0) { ::history (m_history, &m_history_event, H_ENTER, line); new_line.assign (line, line_len); // Omit the newline } else { retval = IOChannel::eLibeditGetInputEmpty; // Someone just hit ENTER, return the empty string new_line.clear(); } // Return true to indicate success even if a string is empty return retval; } else { retval = (line_len == 0 ? IOChannel::eLibeditGetInputEOF : IOChannel::eLibeditGetInputResultError); } } // Return false to indicate failure. This can happen when the file handle // is closed (EOF). new_line.clear(); return retval; } void * IOChannel::IOReadThread (void *ptr) { IOChannel *myself = static_cast (ptr); myself->Run(); return NULL; } void IOChannel::Run () { SBListener listener("IOChannel::Run"); std::string new_line; SBBroadcaster interpreter_broadcaster (m_driver->GetDebugger().GetCommandInterpreter().GetBroadcaster()); listener.StartListeningForEvents (interpreter_broadcaster, SBCommandInterpreter::eBroadcastBitResetPrompt | SBCommandInterpreter::eBroadcastBitThreadShouldExit | SBCommandInterpreter::eBroadcastBitQuitCommandReceived); listener.StartListeningForEvents (*this, IOChannel::eBroadcastBitThreadShouldExit); listener.StartListeningForEvents (*m_driver, Driver::eBroadcastBitReadyForInput | Driver::eBroadcastBitThreadShouldExit); // Let anyone know that the IO channel is up and listening and ready for events BroadcastEventByType (eBroadcastBitThreadDidStart); bool done = false; while (!done) { SBEvent event; listener.WaitForEvent (UINT32_MAX, event); if (!event.IsValid()) continue; const uint32_t event_type = event.GetType(); if (event.GetBroadcaster().IsValid()) { if (event.BroadcasterMatchesPtr (m_driver)) { if (event_type & Driver::eBroadcastBitReadyForInput) { std::string line; if (CommandQueueIsEmpty()) { IOChannel::LibeditGetInputResult getline_result = LibeditGetInput(line); if (getline_result == IOChannel::eLibeditGetInputEOF) { // EOF occurred // pretend that a quit was typed so the user gets a potential // chance to confirm line.assign("quit"); } else if (getline_result == IOChannel::eLibeditGetInputResultError || getline_result == IOChannel::eLibeditGetInputResultUnknown) { // some random error occurred, exit and don't ask because the state might be corrupt done = true; continue; } } else { GetCommandFromQueue (line); } // TO BE DONE: FIGURE OUT WHICH COMMANDS SHOULD NOT BE REPEATED IF USER PRESSES PLAIN 'RETURN' // AND TAKE CARE OF THAT HERE. SBEvent line_event(IOChannel::eBroadcastBitHasUserInput, line.c_str(), line.size()); BroadcastEvent (line_event); } else if (event_type & Driver::eBroadcastBitThreadShouldExit) { done = true; continue; } } else if (event.BroadcasterMatchesRef (interpreter_broadcaster)) { switch (event_type) { case SBCommandInterpreter::eBroadcastBitResetPrompt: { const char *new_prompt = SBEvent::GetCStringFromEvent (event); if (new_prompt) g_prompt_map[m_edit_line] = new_prompt; } break; case SBCommandInterpreter::eBroadcastBitThreadShouldExit: case SBCommandInterpreter::eBroadcastBitQuitCommandReceived: done = true; break; } } else if (event.BroadcasterMatchesPtr (this)) { if (event_type & IOChannel::eBroadcastBitThreadShouldExit) { done = true; continue; } } } } BroadcastEventByType (IOChannel::eBroadcastBitThreadDidExit); m_driver = NULL; m_read_thread = 0; } bool IOChannel::Start () { if (IS_VALID_LLDB_HOST_THREAD(m_read_thread)) return true; m_read_thread = SBHostOS::ThreadCreate ("", IOChannel::IOReadThread, this, NULL); return (IS_VALID_LLDB_HOST_THREAD(m_read_thread)); } bool IOChannel::Stop () { if (!IS_VALID_LLDB_HOST_THREAD(m_read_thread)) return true; BroadcastEventByType (eBroadcastBitThreadShouldExit); // Don't call Host::ThreadCancel since el_gets won't respond to this // function call -- the thread will just die and all local variables in // IOChannel::Run() won't get destructed down which is bad since there is // a local listener holding onto broadcasters... To ensure proper shutdown, // a ^D (control-D) sequence (0x04) should be written to other end of the // the "in" file handle that was passed into the contructor as closing the // file handle doesn't seem to make el_gets() exit.... return SBHostOS::ThreadJoin (m_read_thread, NULL, NULL); } void IOChannel::RefreshPrompt () { // If we are not in the middle of getting input from the user, there is no need to // refresh the prompt. IOLocker locker (m_output_mutex); if (! IsGettingCommand()) return; // If we haven't finished writing the prompt, there's no need to refresh it. if (m_expecting_prompt) return; if (m_refresh_request_pending) return; ::el_set (m_edit_line, EL_REFRESH); m_refresh_request_pending = true; } void IOChannel::OutWrite (const char *buffer, size_t len, bool asynchronous) { if (len == 0 || buffer == NULL) return; // We're in the process of exiting -- IOChannel::Run() has already completed // and set m_driver to NULL - it is time for us to leave now. We might not // print the final ^D to stdout in this case. We need to do some re-work on // how the I/O streams are managed at some point. if (m_driver == NULL) { return; } // Use the mutex to make sure OutWrite and ErrWrite do not interfere with each other's output. IOLocker locker (m_output_mutex); if (m_driver->EditlineReaderIsTop() && asynchronous) ::fwrite (undo_prompt_string, 1, 4, m_out_file); ::fwrite (buffer, 1, len, m_out_file); if (asynchronous) m_driver->GetDebugger().NotifyTopInputReader (eInputReaderAsynchronousOutputWritten); } void IOChannel::ErrWrite (const char *buffer, size_t len, bool asynchronous) { if (len == 0 || buffer == NULL) return; // Use the mutex to make sure OutWrite and ErrWrite do not interfere with each other's output. IOLocker locker (m_output_mutex); if (asynchronous) ::fwrite (undo_prompt_string, 1, 4, m_err_file); ::fwrite (buffer, 1, len, m_err_file); if (asynchronous) m_driver->GetDebugger().NotifyTopInputReader (eInputReaderAsynchronousOutputWritten); } void IOChannel::AddCommandToQueue (const char *command) { m_command_queue.push (std::string(command)); } bool IOChannel::GetCommandFromQueue (std::string &cmd) { if (m_command_queue.empty()) return false; cmd.swap(m_command_queue.front()); m_command_queue.pop (); return true; } int IOChannel::CommandQueueSize () const { return m_command_queue.size(); } void IOChannel::ClearCommandQueue () { while (!m_command_queue.empty()) m_command_queue.pop(); } bool IOChannel::CommandQueueIsEmpty () const { return m_command_queue.empty(); } bool IOChannel::IsGettingCommand () const { return m_getting_command; } void IOChannel::SetGettingCommand (bool new_value) { m_getting_command = new_value; } IOLocker::IOLocker (pthread_mutex_t &mutex) : m_mutex_ptr (&mutex) { if (m_mutex_ptr) ::pthread_mutex_lock (m_mutex_ptr); } IOLocker::~IOLocker () { if (m_mutex_ptr) ::pthread_mutex_unlock (m_mutex_ptr); }