Initial checkin of lldb code from internal Apple repo.

llvm-svn: 105619
diff --git a/lldb/tools/driver/IOChannel.cpp b/lldb/tools/driver/IOChannel.cpp
new file mode 100644
index 0000000..a83e1b6
--- /dev/null
+++ b/lldb/tools/driver/IOChannel.cpp
@@ -0,0 +1,449 @@
+//===-- 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 <map>
+
+#include <LLDB/SBCommandInterpreter.h>
+#include <LLDB/SBDebugger.h>
+#include <LLDB/SBError.h>
+#include <LLDB/SBEvent.h>
+#include <LLDB/SBFileSpec.h>
+#include <LLDB/SBHostOS.h>
+#include <LLDB/SBListener.h>
+#include <LLDB/SBStringList.h>
+
+using namespace lldb;
+
+typedef std::map<EditLine *, std::string> PromptMap;
+const char *g_default_prompt = "(lldb) ";
+PromptMap g_prompt_map;
+
+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();
+}
+
+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;
+    }
+}
+
+unsigned char
+IOChannel::HandleCompletion (EditLine *e, int ch)
+{
+    assert (e == m_edit_line);
+
+    const LineInfo *line_info  = el_line(m_edit_line);
+    SBStringList completions;
+    size_t page_size = 40;
+
+    int num_completions
+        = SBDebugger::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;
+    }
+
+    // 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));
+        if (num_completions < page_size)
+        {
+            for (int i = 1; i < num_elements; i++)
+            {
+                const char *completion_str = completions.GetStringAtIndex(i);
+                OutWrite("\n\t", 2);
+                OutWrite(completion_str, strlen (completion_str));
+            }
+            OutWrite ("\n", 1);
+        }
+        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++)
+                {
+                    const char *completion_str = completions.GetStringAtIndex(cur_pos);
+                    OutWrite("\n\t", 2);
+                    OutWrite(completion_str, strlen (completion_str));
+                }
+
+                if (cur_pos >= num_elements)
+                {
+                    OutWrite("\n", 1);
+                    break;
+                }
+
+                OutWrite("\nMore (Y/n/a): ", strlen ("\nMore (Y/n/a): "));
+                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 *in,
+    FILE *out,
+    FILE *err,
+    Driver *driver
+) :
+    SBBroadcaster ("IOChannel"),
+    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_edit_line (::el_init (SBHostOS::GetProgramFileSpec().GetFileName(), in, out, err)),
+    m_history (history_init()),
+    m_completion_key ("\t")
+{
+    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);
+
+    // Source $PWD/.editrc then $HOME/.editrc
+    ::el_source (m_edit_line, NULL);
+
+    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_CLIENTDATA, this);
+
+    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);
+}
+
+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;
+    }
+}
+
+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 (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);
+        }
+    }
+}
+
+bool
+IOChannel::LibeditGetInput (std::string &new_line)
+{
+    if (m_edit_line != NULL)
+    {
+        int line_len = 0;
+        const char *line = ::el_gets (m_edit_line, &line_len);
+        if (line)
+        {
+            // 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
+            {
+                // Someone just hit ENTER, return the empty string
+                new_line.clear();
+            }
+            // Return true to indicate success even if a string is empty
+            return true;
+        }
+    }
+    // Return false to indicate failure. This can happen when the file handle
+    // is closed (EOF).
+    new_line.clear();
+    return false;
+}
+
+void *
+IOChannel::IOReadThread (void *ptr)
+{
+    IOChannel *myself = static_cast<IOChannel *> (ptr);
+    myself->Run();
+    return NULL;
+}
+
+void
+IOChannel::Run ()
+{
+    SBListener listener("IOChannel::Run");
+    std::string new_line;
+
+    SBBroadcaster interpreter_broadcaster (SBDebugger::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())
+                    {
+                        if (LibeditGetInput(line) == false)
+                        {
+                            // EOF or some other file error occurred
+                            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;
+                    break;
+                }
+            }
+            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;
+                    break;
+                }
+            }
+        }
+    }
+    BroadcastEventByType (IOChannel::eBroadcastBitThreadDidExit);
+    m_driver = NULL;
+    m_read_thread = NULL;
+}
+
+bool
+IOChannel::Start ()
+{
+    if (m_read_thread != LLDB_INVALID_HOST_THREAD)
+        return true;
+
+    m_read_thread = SBHostOS::ThreadCreate ("<lldb.driver.commandline_io>", IOChannel::IOReadThread, this,
+                                            NULL);
+
+    return (m_read_thread != LLDB_INVALID_HOST_THREAD);
+}
+
+bool
+IOChannel::Stop ()
+{
+    if (m_read_thread == NULL)
+        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 ()
+{
+    ::el_set (m_edit_line, EL_REFRESH);
+}
+
+void
+IOChannel::OutWrite (const char *buffer, size_t len)
+{
+    if (len == 0)
+        return;
+    ::fwrite (buffer, 1, len, m_out_file);
+}
+
+void
+IOChannel::ErrWrite (const char *buffer, size_t len)
+{
+    if (len == 0)
+        return;
+    ::fwrite (buffer, 1, len, m_err_file);
+}
+
+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();
+}