//===-- 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 <iomanip>
#include <iostream>
#include <limits.h>

#include "lldb/Host/Editline.h"
#include "lldb/Host/ConnectionFileDescriptor.h"
#include "lldb/Core/Error.h"
#include "lldb/Core/StringList.h"
#include "lldb/Core/StreamString.h"
#include "lldb/Host/FileSpec.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/Mutex.h"
#include "lldb/Utility/LLDBAssert.h"

using namespace lldb_private;
using namespace lldb_private::line_editor;

// Workaround for what looks like an OS X-specific issue, but other platforms
// may benefit from something similar if issues arise.  The libedit library
// doesn't explicitly initialize the curses termcap library, which it gets away
// with until TERM is set to VT100 where it stumbles over an implementation
// assumption that may not exist on other platforms.  The setupterm() function
// would normally require headers that don't work gracefully in this context, so
// the function declaraction has been hoisted here.
#if defined(__APPLE__)
extern "C" {
    int setupterm(char *term, int fildes, int *errret);
}
#define USE_SETUPTERM_WORKAROUND
#endif

// Editline uses careful cursor management to achieve the illusion of editing a multi-line block of text
// with a single line editor.  Preserving this illusion requires fairly careful management of cursor
// state.  Read and understand the relationship between DisplayInput(), MoveCursor(), SetCurrentLine(),
// and SaveEditedLine() before making changes.

#define ESCAPE "\x1b"
#define ANSI_FAINT ESCAPE "[2m"
#define ANSI_UNFAINT ESCAPE "[22m"
#define ANSI_CLEAR_BELOW ESCAPE "[J"
#define ANSI_CLEAR_RIGHT ESCAPE "[K"
#define ANSI_SET_COLUMN_N ESCAPE "[%dG"
#define ANSI_UP_N_ROWS ESCAPE "[%dA"
#define ANSI_DOWN_N_ROWS ESCAPE "[%dB"

#if LLDB_EDITLINE_USE_WCHAR

#define EditLineConstString(str) L##str
#define EditLineStringFormatSpec "%ls"

#else

#define EditLineConstString(str) str
#define EditLineStringFormatSpec "%s"

// use #defines so wide version functions and structs will resolve to old versions
// for case of libedit not built with wide char support
#define history_w history
#define history_winit history_init
#define history_wend history_end
#define HistoryW History
#define HistEventW HistEvent
#define LineInfoW LineInfo

#define el_wgets el_gets
#define el_wgetc el_getc
#define el_wpush el_push
#define el_wparse el_parse
#define el_wset  el_set
#define el_wget  el_get
#define el_wline el_line
#define el_winsertstr el_insertstr
#define  el_wdeletestr el_deletestr

#endif // #if LLDB_EDITLINE_USE_WCHAR

bool
IsOnlySpaces (const EditLineStringType & content)
{
    for (wchar_t ch : content)
    {
        if (ch != EditLineCharType(' ')) 
            return false;
    }
    return true;
}

EditLineStringType
CombineLines (const std::vector<EditLineStringType> & lines)
{
    EditLineStringStreamType combined_stream;
    for (EditLineStringType line : lines)
    {
        combined_stream << line.c_str() << "\n";
    }
    return combined_stream.str();
}

std::vector<EditLineStringType>
SplitLines (const EditLineStringType & input)
{
    std::vector<EditLineStringType> result;
    size_t start = 0;
    while (start < input.length()) 
    {
        size_t end = input.find ('\n', start);
        if (end == std::string::npos)
        {
            result.insert (result.end(), input.substr (start));
            break;
        }
        result.insert (result.end(), input.substr (start, end - start));
        start = end + 1;
    }
    return result;
}

EditLineStringType
FixIndentation (const EditLineStringType & line, int indent_correction)
{
    if (indent_correction == 0) 
        return line;
    if (indent_correction < 0) 
        return line.substr (-indent_correction);
    return EditLineStringType (indent_correction, EditLineCharType(' ')) + line;
}

int
GetIndentation (const EditLineStringType & line)
{
    int space_count = 0;
    for (EditLineCharType ch : line)
    {
        if (ch != EditLineCharType(' ')) 
            break;
        ++space_count;
    }
    return space_count;
}

bool
IsInputPending (FILE * file)
{
    // FIXME: This will be broken on Windows if we ever re-enable Editline.  You can't use select
    // on something that isn't a socket.  This will have to be re-written to not use a FILE*, but
    // instead use some kind of yet-to-be-created abstraction that select-like functionality on
    // non-socket objects.
    const int fd = fileno (file);
    fd_set fds;
    FD_ZERO (&fds);
    FD_SET (fd, &fds);
    timeval timeout = { 0, 0 };
    return select (fd + 1, &fds, NULL, NULL, &timeout);
}

namespace lldb_private
{
    namespace line_editor
    {
        typedef std::weak_ptr<EditlineHistory> EditlineHistoryWP;

        // EditlineHistory objects are sometimes shared between multiple
        // Editline instances with the same program name.
        
        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_winit();
                history_w (m_history, &m_event, H_SETSIZE, size);
                if (unique_entries)
                    history_w (m_history, &m_event, H_SETUNIQUE, 1);
            }

            const char *
            GetHistoryFilePath()
            {
                if (m_path.empty() && m_history && !m_prefix.empty())
                {
                    FileSpec parent_path{"~/.lldb", true};
                    char history_path[PATH_MAX];
                    if (FileSystem::MakeDirectory(parent_path, lldb::eFilePermissionsDirectoryDefault).Success())
                    {
                        snprintf (history_path, sizeof (history_path), "~/.lldb/%s-history", m_prefix.c_str());
                    }
                    else
                    {
                        snprintf (history_path, sizeof (history_path), "~/%s-widehistory", m_prefix.c_str());
                    }
                    m_path = FileSpec (history_path, true).GetPath();
                }
                if (m_path.empty())
                    return NULL;
                return m_path.c_str();
            }
            
        public:
            
            ~EditlineHistory()
            {
                Save();
                
                if (m_history)
                {
                    history_wend (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;
            }
            
            HistoryW *
            GetHistoryPtr ()
            {
                return m_history;
            }
            
            void
            Enter (const EditLineCharType *line_cstr)
            {
                if (m_history)
                    history_w (m_history, &m_event, H_ENTER, line_cstr);
            }
            
            bool
            Load ()
            {
                if (m_history)
                {
                    const char *path = GetHistoryFilePath();
                    if (path)
                    {
                        history_w (m_history, &m_event, H_LOAD, path);
                        return true;
                    }
                }
                return false;
            }
            
            bool
            Save ()
            {
                if (m_history)
                {
                    const char *path = GetHistoryFilePath();
                    if (path)
                    {
                        history_w (m_history, &m_event, H_SAVE, path);
                        return true;
                    }
                }
                return false;
            }
            
        protected:
            HistoryW * m_history;   // The history object
            HistEventW 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
        };
    }
}

//------------------------------------------------------------------
// Editline private methods
//------------------------------------------------------------------

void
Editline::SetBaseLineNumber (int line_number)
{
    std::stringstream line_number_stream;
    line_number_stream << line_number;
    m_base_line_number = line_number;
    m_line_number_digits = std::max (3, (int)line_number_stream.str().length() + 1);
}

std::string
Editline::PromptForIndex (int line_index)
{
    bool use_line_numbers = m_multiline_enabled && m_base_line_number > 0;
    std::string prompt = m_set_prompt;
    if (use_line_numbers && prompt.length() == 0)
    {
        prompt = ": ";
    }
    std::string continuation_prompt = prompt;
    if (m_set_continuation_prompt.length() > 0)
    {
        continuation_prompt = m_set_continuation_prompt;
        
        // Ensure that both prompts are the same length through space padding
        while (continuation_prompt.length() < prompt.length())
        {
            continuation_prompt += ' ';
        }
        while (prompt.length() < continuation_prompt.length())
        {
            prompt += ' ';
        }
    }
    
    if (use_line_numbers)
    {
        StreamString prompt_stream;
        prompt_stream.Printf("%*d%s", m_line_number_digits, m_base_line_number + line_index,
                             (line_index == 0) ? prompt.c_str() : continuation_prompt.c_str());
        return std::move (prompt_stream.GetString());
    }
    return (line_index == 0) ? prompt : continuation_prompt;
}

void
Editline::SetCurrentLine (int line_index)
{
    m_current_line_index = line_index;
    m_current_prompt = PromptForIndex (line_index);
}

int
Editline::GetPromptWidth()
{
    return (int)PromptForIndex (0).length();
}

bool
Editline::IsEmacs()
{
    const char * editor;
    el_get (m_editline, EL_EDITOR, &editor);
    return editor[0] == 'e';
}

bool
Editline::IsOnlySpaces()
{
    const LineInfoW * info = el_wline (m_editline);
    for (const EditLineCharType * character = info->buffer; character < info->lastchar; character++)
    {
        if (*character != ' ') 
            return false;
    }
    return true;
}

int
Editline::GetLineIndexForLocation (CursorLocation location, int cursor_row)
{
    int line = 0;
    if (location == CursorLocation::EditingPrompt || location == CursorLocation::BlockEnd ||
        location == CursorLocation::EditingCursor)
    {
        for (unsigned index = 0; index < m_current_line_index; index++)
        {
            line += CountRowsForLine (m_input_lines[index]);
        }
        if (location == CursorLocation::EditingCursor)
        {
            line += cursor_row;
        }
        else if (location == CursorLocation::BlockEnd)
        {
            for (unsigned index = m_current_line_index; index < m_input_lines.size(); index++)
            {
                line += CountRowsForLine (m_input_lines[index]);
            }
            --line;
        }
    }
    return line;
}

void
Editline::MoveCursor (CursorLocation from, CursorLocation to)
{
    const LineInfoW * info = el_wline (m_editline);
    int editline_cursor_position = (int)((info->cursor - info->buffer) + GetPromptWidth());
    int editline_cursor_row = editline_cursor_position / m_terminal_width;
    
    // Determine relative starting and ending lines
    int fromLine = GetLineIndexForLocation (from, editline_cursor_row);
    int toLine = GetLineIndexForLocation (to, editline_cursor_row);
    if (toLine != fromLine)
    {
        fprintf (m_output_file, (toLine > fromLine) ? ANSI_DOWN_N_ROWS : ANSI_UP_N_ROWS, std::abs (toLine - fromLine));
    }
    
    // Determine target column
    int toColumn = 1;
    if (to == CursorLocation::EditingCursor)
    {
        toColumn = editline_cursor_position - (editline_cursor_row * m_terminal_width) + 1;
    }
    else if (to == CursorLocation::BlockEnd)
    {
        toColumn = ((m_input_lines[m_input_lines.size() - 1].length() + GetPromptWidth()) % 80) + 1;
    }
    fprintf (m_output_file, ANSI_SET_COLUMN_N, toColumn);
}

void
Editline::DisplayInput (int firstIndex)
{
    fprintf (m_output_file, ANSI_SET_COLUMN_N ANSI_CLEAR_BELOW, 1);
    int line_count = (int)m_input_lines.size();
    const char *faint  = m_color_prompts ? ANSI_FAINT : "";
    const char *unfaint  = m_color_prompts ? ANSI_UNFAINT : "";
    
    for (int index = firstIndex; index < line_count; index++)
    {
        fprintf (m_output_file, "%s" "%s" "%s" EditLineStringFormatSpec " ",
                faint,
                PromptForIndex (index).c_str(),
                unfaint,
                m_input_lines[index].c_str());
        if (index < line_count - 1) 
            fprintf (m_output_file, "\n");
    }
}


int
Editline::CountRowsForLine (const EditLineStringType & content)
{
    auto prompt = PromptForIndex (0); // Prompt width is constant during an edit session
    int line_length = (int)(content.length() + prompt.length());
    return (line_length / m_terminal_width) + 1;
}

void
Editline::SaveEditedLine()
{
    const LineInfoW * info = el_wline (m_editline);
    m_input_lines[m_current_line_index] = EditLineStringType (info->buffer, info->lastchar - info->buffer);
}

StringList
Editline::GetInputAsStringList(int line_count)
{
    StringList lines;
    for (EditLineStringType line : m_input_lines)
    {
        if (line_count == 0) 
            break;
#if LLDB_EDITLINE_USE_WCHAR
        lines.AppendString (m_utf8conv.to_bytes (line));
#else
        lines.AppendString(line);
#endif
        --line_count;
    }
    return lines;
}

unsigned char
Editline::RecallHistory (bool earlier)
{
    if (!m_history_sp || !m_history_sp->IsValid()) 
        return CC_ERROR;
    
    HistoryW * pHistory = m_history_sp->GetHistoryPtr();
    HistEventW history_event;
    std::vector<EditLineStringType> new_input_lines;
    
    // Treat moving from the "live" entry differently
    if (!m_in_history)
    {
        if (earlier == false) 
            return CC_ERROR; // Can't go newer than the "live" entry
        if (history_w (pHistory, &history_event, H_FIRST) == -1) 
            return CC_ERROR;

        // Save any edits to the "live" entry in case we return by moving forward in history
        // (it would be more bash-like to save over any current entry, but libedit doesn't
        // offer the ability to add entries anywhere except the end.)
        SaveEditedLine();
        m_live_history_lines = m_input_lines;
        m_in_history = true;
    }
    else
    {
        if (history_w (pHistory, &history_event, earlier ? H_NEXT : H_PREV) == -1)
        {
            // Can't move earlier than the earliest entry
            if (earlier) 
                return CC_ERROR;

            // ... but moving to newer than the newest yields the "live" entry
            new_input_lines = m_live_history_lines;
            m_in_history = false;
        }
    }
    
    // If we're pulling the lines from history, split them apart
    if (m_in_history) 
        new_input_lines = SplitLines (history_event.str);

    // Erase the current edit session and replace it with a new one
    MoveCursor (CursorLocation::EditingCursor, CursorLocation::BlockStart);
    m_input_lines = new_input_lines;
    DisplayInput();
    
    // Prepare to edit the last line when moving to previous entry, or the first line
    // when moving to next entry
    SetCurrentLine (m_current_line_index = earlier ? (int)m_input_lines.size() - 1 : 0);
    MoveCursor (CursorLocation::BlockEnd, CursorLocation::EditingPrompt);
    return CC_NEWLINE;
}

int
Editline::GetCharacter (EditLineCharType * c)
{
    const LineInfoW * info = el_wline (m_editline);
    
    // Paint a faint version of the desired prompt over the version libedit draws
    // (will only be requested if colors are supported)
    if (m_needs_prompt_repaint)
    {
        MoveCursor (CursorLocation::EditingCursor, CursorLocation::EditingPrompt);
        fprintf (m_output_file, "%s" "%s" "%s", ANSI_FAINT, Prompt(), ANSI_UNFAINT);
        MoveCursor (CursorLocation::EditingPrompt, CursorLocation::EditingCursor);
        m_needs_prompt_repaint = false;
    }
    
    if (m_multiline_enabled)
    {
        // Detect when the number of rows used for this input line changes due to an edit
        int lineLength = (int)((info->lastchar - info->buffer) + GetPromptWidth());
        int new_line_rows = (lineLength / m_terminal_width) + 1;
        if (m_current_line_rows != -1 && new_line_rows != m_current_line_rows)
        {
            // Respond by repainting the current state from this line on
            MoveCursor (CursorLocation::EditingCursor, CursorLocation::EditingPrompt);
            SaveEditedLine();
            DisplayInput (m_current_line_index);
            MoveCursor (CursorLocation::BlockEnd, CursorLocation::EditingCursor);
        }
        m_current_line_rows = new_line_rows;
    }
    
    // Read an actual character
    while (true)
    {
        lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess;
        char ch = 0;

        // This mutex is locked by our caller (GetLine). Unlock it while we read a character
        // (blocking operation), so we do not hold the mutex indefinitely. This gives a chance
        // for someone to interrupt us. After Read returns, immediately lock the mutex again and
        // check if we were interrupted.
        m_output_mutex.Unlock();
        int read_count = m_input_connection.Read(&ch, 1, UINT32_MAX, status, NULL);
        m_output_mutex.Lock();
        if (m_editor_status == EditorStatus::Interrupted)
        {
            while (read_count > 0 && status == lldb::eConnectionStatusSuccess)
                read_count = m_input_connection.Read(&ch, 1, UINT32_MAX, status, NULL);
            lldbassert(status == lldb::eConnectionStatusInterrupted);
            return 0;
        }

        if (read_count)
        {
#if LLDB_EDITLINE_USE_WCHAR
            // After the initial interruptible read, this is guaranteed not to block
            ungetc (ch, m_input_file);
            *c = fgetwc (m_input_file);
            if (*c != WEOF) 
                return 1;
#else
            *c = ch;
            if(ch != (char)EOF) 
                return 1;
#endif
        }
        else
        {
            switch (status)
            {
                case lldb::eConnectionStatusSuccess:         // Success
                    break;
                    
                case lldb::eConnectionStatusInterrupted:
                    lldbassert(0 && "Interrupts should have been handled above.");

                case lldb::eConnectionStatusError:           // Check GetError() for details
                case lldb::eConnectionStatusTimedOut:        // Request timed out
                case lldb::eConnectionStatusEndOfFile:       // End-of-file encountered
                case lldb::eConnectionStatusNoConnection:    // No connection
                case lldb::eConnectionStatusLostConnection:  // Lost connection while connected to a valid connection
                    m_editor_status = EditorStatus::EndOfInput;
                    return 0;
            }
        }
    }
}

const char *
Editline::Prompt()
{
    if (m_color_prompts) 
        m_needs_prompt_repaint = true;
    return m_current_prompt.c_str();
}

unsigned char
Editline::BreakLineCommand (int ch)
{
    // Preserve any content beyond the cursor, truncate and save the current line
    const LineInfoW * info = el_wline (m_editline);
    auto current_line = EditLineStringType (info->buffer, info->cursor - info->buffer);
    auto new_line_fragment = EditLineStringType (info->cursor, info->lastchar - info->cursor);
    m_input_lines[m_current_line_index] = current_line;
    
    // Ignore whitespace-only extra fragments when breaking a line
    if (::IsOnlySpaces (new_line_fragment)) 
        new_line_fragment = EditLineConstString("");

    // Establish the new cursor position at the start of a line when inserting a line break
    m_revert_cursor_index = 0;

    // Don't perform end of input detection or automatic formatting when pasting
    if (!IsInputPending (m_input_file))
    {
        // If this is the end of the last line, treat this as a potential exit
        if (m_current_line_index == m_input_lines.size() - 1 && new_line_fragment.length() == 0)
        {
            bool end_of_input = true;
            if (m_is_input_complete_callback) 
            {
                SaveEditedLine();
                auto lines = GetInputAsStringList();
                end_of_input = m_is_input_complete_callback (this, lines, m_is_input_complete_callback_baton);

                // The completion test is allowed to change the input lines when complete
                if (end_of_input)
                {
                    m_input_lines.clear();
                    for (unsigned index = 0; index < lines.GetSize(); index++)
                    {
#if LLDB_EDITLINE_USE_WCHAR
                        m_input_lines.insert (m_input_lines.end(), m_utf8conv.from_bytes (lines[index]));
#else
                        m_input_lines.insert (m_input_lines.end(), lines[index]);
#endif
                    }
                }
            }
            if (end_of_input)
            {
                fprintf (m_output_file, "\n");
                m_editor_status = EditorStatus::Complete;
                return CC_NEWLINE;
            }
        }
        
        // Apply smart indentation
        if (m_fix_indentation_callback) 
        {
            StringList lines = GetInputAsStringList (m_current_line_index + 1);
#if LLDB_EDITLINE_USE_WCHAR
            lines.AppendString (m_utf8conv.to_bytes (new_line_fragment));
#else
            lines.AppendString (new_line_fragment);
#endif
            
            int indent_correction = m_fix_indentation_callback (this, lines, 0, m_fix_indentation_callback_baton);
            new_line_fragment = FixIndentation(new_line_fragment, indent_correction);
            m_revert_cursor_index = GetIndentation(new_line_fragment);
        }
    }
    
    // Insert the new line and repaint everything from the split line on down
    m_input_lines.insert (m_input_lines.begin() + m_current_line_index + 1, new_line_fragment);
    MoveCursor (CursorLocation::EditingCursor, CursorLocation::EditingPrompt);
    DisplayInput (m_current_line_index);
    
    // Reposition the cursor to the right line and prepare to edit the new line
    SetCurrentLine (m_current_line_index + 1);
    MoveCursor (CursorLocation::BlockEnd, CursorLocation::EditingPrompt);
    return CC_NEWLINE;
}

unsigned char
Editline::DeleteNextCharCommand (int ch)
{
    LineInfoW * info = const_cast<LineInfoW *>(el_wline (m_editline));
    
    // Just delete the next character normally if possible
    if (info->cursor < info->lastchar) 
    {
        info->cursor++;
        el_deletestr (m_editline, 1);
        return CC_REFRESH;
    }

    // Fail when at the end of the last line, except when ^D is pressed on
    // the line is empty, in which case it is treated as EOF
    if (m_current_line_index == m_input_lines.size() - 1)
    {
        if (ch == 4 && info->buffer == info->lastchar)
        {
            fprintf (m_output_file, "^D\n");
            m_editor_status = EditorStatus::EndOfInput;
            return CC_EOF;
        }
        return CC_ERROR;
    }
    
    // Prepare to combine this line with the one below
    MoveCursor (CursorLocation::EditingCursor, CursorLocation::EditingPrompt);
    
    // Insert the next line of text at the cursor and restore the cursor position
    const EditLineCharType * cursor = info->cursor;
    el_winsertstr (m_editline, m_input_lines[m_current_line_index + 1].c_str());
    info->cursor = cursor;
    SaveEditedLine();
    
    // Delete the extra line
    m_input_lines.erase (m_input_lines.begin() + m_current_line_index + 1);
    
    // Clear and repaint from this line on down
    DisplayInput (m_current_line_index);
    MoveCursor (CursorLocation::BlockEnd, CursorLocation::EditingCursor);
    return CC_REFRESH;
}

unsigned char
Editline::DeletePreviousCharCommand (int ch)
{
    LineInfoW * info = const_cast<LineInfoW *>(el_wline (m_editline));
    
    // Just delete the previous character normally when not at the start of a line
    if (info->cursor > info->buffer) 
    {
        el_deletestr (m_editline, 1);
        return CC_REFRESH;
    }
    
    // No prior line and no prior character?  Let the user know
    if (m_current_line_index == 0) 
        return CC_ERROR;
    
    // No prior character, but prior line?  Combine with the line above
    SaveEditedLine();
    SetCurrentLine (m_current_line_index - 1);
    auto priorLine = m_input_lines[m_current_line_index];
    m_input_lines.erase (m_input_lines.begin() + m_current_line_index);
    m_input_lines[m_current_line_index] = priorLine + m_input_lines[m_current_line_index];
    
    // Repaint from the new line down
    fprintf (m_output_file, ANSI_UP_N_ROWS ANSI_SET_COLUMN_N, CountRowsForLine (priorLine), 1);
    DisplayInput (m_current_line_index);
    
    // Put the cursor back where libedit expects it to be before returning to editing
    // by telling libedit about the newly inserted text
    MoveCursor (CursorLocation::BlockEnd, CursorLocation::EditingPrompt);
    el_winsertstr (m_editline, priorLine.c_str());
    return CC_REDISPLAY;
}

unsigned char
Editline::PreviousLineCommand (int ch)
{
    SaveEditedLine();

    if (m_current_line_index == 0) {
        return RecallHistory (true);
    }
    
    // Start from a known location
    MoveCursor (CursorLocation::EditingCursor, CursorLocation::EditingPrompt);
    
    // Treat moving up from a blank last line as a deletion of that line
    if (m_current_line_index == m_input_lines.size() - 1 && IsOnlySpaces()) 
    {
        m_input_lines.erase (m_input_lines.begin() + m_current_line_index);
        fprintf (m_output_file, ANSI_CLEAR_BELOW);
    }
    
    SetCurrentLine (m_current_line_index - 1);
    fprintf (m_output_file, ANSI_UP_N_ROWS ANSI_SET_COLUMN_N,
            CountRowsForLine (m_input_lines[m_current_line_index]), 1);
    return CC_NEWLINE;
}

unsigned char
Editline::NextLineCommand (int ch)
{
    SaveEditedLine();

    // Handle attempts to move down from the last line
    if (m_current_line_index == m_input_lines.size() - 1) 
    {
        // Don't add an extra line if the existing last line is blank, move through history instead
        if (IsOnlySpaces()) 
        {
            return RecallHistory (false);
        }
        
        // Determine indentation for the new line
        int indentation = 0;
        if (m_fix_indentation_callback)
        {
            StringList lines = GetInputAsStringList();
            lines.AppendString("");
            indentation = m_fix_indentation_callback (this, lines, 0, m_fix_indentation_callback_baton);
        }
        m_input_lines.insert (m_input_lines.end(), EditLineStringType (indentation, EditLineCharType(' ')));
    }
    
    // Move down past the current line using newlines to force scrolling if needed
    SetCurrentLine (m_current_line_index + 1);
    const LineInfoW * info = el_wline (m_editline);
    int cursor_position = (int)((info->cursor - info->buffer) + GetPromptWidth());
    int cursor_row = cursor_position / m_terminal_width;
    for (int line_count = 0; line_count < m_current_line_rows - cursor_row; line_count++) 
    {
        fprintf (m_output_file, "\n");
    }
    return CC_NEWLINE;
}

unsigned char
Editline::FixIndentationCommand (int ch)
{
    if (!m_fix_indentation_callback)
        return CC_NORM;

    // Insert the character typed before proceeding
    EditLineCharType inserted[] = { (EditLineCharType)ch, 0 };
    el_winsertstr (m_editline, inserted);
    LineInfoW * info = const_cast<LineInfoW *>(el_wline (m_editline));
    int cursor_position = info->cursor - info->buffer;

    // Save the edits and determine the correct indentation level
    SaveEditedLine();
    StringList lines = GetInputAsStringList (m_current_line_index + 1);
    int indent_correction = m_fix_indentation_callback (this, lines, cursor_position, m_fix_indentation_callback_baton);

    // If it is already correct no special work is needed
    if (indent_correction == 0)
        return CC_REFRESH;

    // Change the indentation level of the line
    std::string currentLine = lines.GetStringAtIndex (m_current_line_index);
    if (indent_correction > 0)
    {
        currentLine = currentLine.insert (0, indent_correction, ' ');
    }
    else
    {
        currentLine = currentLine.erase (0, -indent_correction);
    }
#if LLDB_EDITLINE_USE_WCHAR
    m_input_lines[m_current_line_index] = m_utf8conv.from_bytes (currentLine);
#else
    m_input_lines[m_current_line_index] = currentLine;
#endif

    // Update the display to reflect the change
    MoveCursor (CursorLocation::EditingCursor, CursorLocation::EditingPrompt);
    DisplayInput (m_current_line_index);
    
    // Reposition the cursor back on the original line and prepare to restart editing
    // with a new cursor position
    SetCurrentLine (m_current_line_index);
    MoveCursor (CursorLocation::BlockEnd, CursorLocation::EditingPrompt);
    m_revert_cursor_index = cursor_position + indent_correction;
    return CC_NEWLINE;
}

unsigned char
Editline::RevertLineCommand (int ch)
{
    el_winsertstr (m_editline, m_input_lines[m_current_line_index].c_str());
    if (m_revert_cursor_index >= 0)
    {
        LineInfoW * info = const_cast<LineInfoW *>(el_wline (m_editline));
        info->cursor = info->buffer + m_revert_cursor_index;
        if (info->cursor > info->lastchar)
        {
            info->cursor = info->lastchar;
        }
        m_revert_cursor_index = -1;
    }
    return CC_REFRESH;
}

unsigned char
Editline::BufferStartCommand (int ch)
{
    SaveEditedLine();
    MoveCursor (CursorLocation::EditingCursor, CursorLocation::BlockStart);
    SetCurrentLine (0);
    m_revert_cursor_index = 0;
    return CC_NEWLINE;
}

unsigned char
Editline::BufferEndCommand (int ch)
{
    SaveEditedLine();
    MoveCursor (CursorLocation::EditingCursor, CursorLocation::BlockEnd);
    SetCurrentLine ((int)m_input_lines.size() - 1);
    MoveCursor (CursorLocation::BlockEnd, CursorLocation::EditingPrompt);
    return CC_NEWLINE;
}

unsigned char
Editline::TabCommand (int ch)
{
    if (m_completion_callback == nullptr) 
        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);
    
    if (num_completions == 0) 
        return CC_ERROR;
    //    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 != nullptr && *completion_str != '\0')
    {
        el_insertstr (m_editline, completion_str);
        return CC_REDISPLAY;
    }
    
    if (num_completions > 1)
    {
        int num_elements = num_completions + 1;
        fprintf (m_output_file, "\n" ANSI_CLEAR_BELOW "Available completions:");
        if (num_completions < page_size)
        {
            for (int i = 1; i < num_elements; i++)
            {
                completion_str = completions.GetStringAtIndex (i);
                fprintf (m_output_file, "\n\t%s", completion_str);
            }
            fprintf (m_output_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 (m_output_file, "\n\t%s", completion_str);
                }
                
                if (cur_pos >= num_elements)
                {
                    fprintf (m_output_file, "\n");
                    break;
                }
                
                fprintf (m_output_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;
            }
        }
        DisplayInput();
        MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);
    }
    return CC_REDISPLAY;
}

void
Editline::ConfigureEditor (bool multiline)
{
    if (m_editline && m_multiline_enabled == multiline) 
        return;
    m_multiline_enabled = multiline;
    
    if (m_editline) 
    {
        // 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 = el_init (m_editor_name.c_str(), m_input_file, m_output_file, m_error_file);
    TerminalSizeChanged();
    
    if (m_history_sp && m_history_sp->IsValid())
    {
        m_history_sp->Load();
        el_wset (m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr());
    }
    el_set (m_editline, EL_CLIENTDATA, this);
    el_set (m_editline, EL_SIGNAL, 0);
    el_set (m_editline, EL_EDITOR, "emacs");
    el_set (m_editline, EL_PROMPT, (EditlinePromptCallbackType)([] (EditLine *editline) {
        return Editline::InstanceFor (editline)->Prompt();
    }));

    el_wset (m_editline, EL_GETCFN,
            (EditlineGetCharCallbackType)([] (EditLine * editline, EditLineCharType * c) {
                return Editline::InstanceFor (editline)->GetCharacter (c);
            }));
    
    // Commands used for multiline support, registered whether or not they're used
    el_set (m_editline, EL_ADDFN, "lldb-break-line", "Insert a line break",
           (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
               return Editline::InstanceFor (editline)->BreakLineCommand (ch);
           }));
    el_set (m_editline, EL_ADDFN, "lldb-delete-next-char", "Delete next character",
           (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
               return Editline::InstanceFor (editline)->DeleteNextCharCommand (ch);
           }));
    el_set (m_editline, EL_ADDFN, "lldb-delete-previous-char", "Delete previous character",
           (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
               return Editline::InstanceFor (editline)->DeletePreviousCharCommand (ch);
           }));
    el_set (m_editline, EL_ADDFN, "lldb-previous-line", "Move to previous line",
           (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
               return Editline::InstanceFor (editline)->PreviousLineCommand (ch);
           }));
    el_set (m_editline, EL_ADDFN, "lldb-next-line", "Move to next line",
           (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
               return Editline::InstanceFor (editline)->NextLineCommand (ch);
           }));
    el_set (m_editline, EL_ADDFN, "lldb-buffer-start", "Move to start of buffer",
           (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
               return Editline::InstanceFor (editline)->BufferStartCommand (ch);
           }));
    el_set (m_editline, EL_ADDFN, "lldb-buffer-end", "Move to end of buffer",
           (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
                return Editline::InstanceFor (editline)->BufferEndCommand (ch);
            }));
    el_set (m_editline, EL_ADDFN, "lldb-fix-indentation", "Fix line indentation",
           (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
                return Editline::InstanceFor (editline)->FixIndentationCommand (ch);
            }));

    // Register the complete callback under two names for compatibility with older clients using
    // custom .editrc files (largely becuase libedit has a bad bug where if you have a bind command
    // that tries to bind to a function name that doesn't exist, it can corrupt the heap and
    // crash your process later.)
    EditlineCommandCallbackType complete_callback = [] (EditLine * editline, int ch) {
        return Editline::InstanceFor (editline)->TabCommand (ch);
    };
    el_set (m_editline, EL_ADDFN, "lldb-complete", "Invoke completion", complete_callback);
    el_set (m_editline, EL_ADDFN, "lldb_complete", "Invoke completion", complete_callback);
    
    // General bindings we don't mind being overridden
    if (!multiline) {
        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 in emacs mode
    el_set (m_editline, EL_BIND, "\t", "lldb-complete", NULL); // Bind TAB to auto complete
    
    // Allow user-specific customization prior to registering bindings we absolutely require
    el_source (m_editline, NULL);

    // Register an internal binding that external developers shouldn't use
    el_set (m_editline, EL_ADDFN, "lldb-revert-line", "Revert line to saved state",
           (EditlineCommandCallbackType)([] (EditLine * editline, int ch) {
               return Editline::InstanceFor (editline)->RevertLineCommand (ch);
           }));

    // Register keys that perform auto-indent correction
    if (m_fix_indentation_callback && m_fix_indentation_callback_chars)
    {
        char bind_key[2] = { 0, 0 };
        const char * indent_chars = m_fix_indentation_callback_chars;
        while (*indent_chars)
        {
            bind_key[0] = *indent_chars;
            el_set (m_editline, EL_BIND, bind_key, "lldb-fix-indentation", NULL);
            ++indent_chars;
        }
    }

    // Multi-line editor bindings
    if (multiline)
    {
        el_set (m_editline, EL_BIND, "\n", "lldb-break-line", NULL);
        el_set (m_editline, EL_BIND, "\r", "lldb-break-line", NULL);
        el_set (m_editline, EL_BIND, "^p", "lldb-previous-line", NULL);
        el_set (m_editline, EL_BIND, "^n", "lldb-next-line", NULL);
        el_set (m_editline, EL_BIND, "^?", "lldb-delete-previous-char", NULL);
        el_set (m_editline, EL_BIND, "^d", "lldb-delete-next-char", NULL);
        el_set (m_editline, EL_BIND, ESCAPE "[3~", "lldb-delete-next-char", NULL);
        el_set (m_editline, EL_BIND, ESCAPE "[\\^", "lldb-revert-line", NULL);

        // Editor-specific bindings
        if (IsEmacs())
        {
            el_set (m_editline, EL_BIND, ESCAPE "<", "lldb-buffer-start", NULL);
            el_set (m_editline, EL_BIND, ESCAPE ">", "lldb-buffer-end", NULL);
            el_set (m_editline, EL_BIND, ESCAPE "[A", "lldb-previous-line", NULL);
            el_set (m_editline, EL_BIND, ESCAPE "[B", "lldb-next-line", NULL);
        }
        else
        {
            el_set (m_editline, EL_BIND, "^H", "lldb-delete-previous-char", NULL);
            
            el_set (m_editline, EL_BIND, "-a", ESCAPE "[A", "lldb-previous-line", NULL);
            el_set (m_editline, EL_BIND, "-a", ESCAPE "[B", "lldb-next-line", NULL);
            el_set (m_editline, EL_BIND, "-a", "x", "lldb-delete-next-char", NULL);
            el_set (m_editline, EL_BIND, "-a", "^H", "lldb-delete-previous-char", NULL);
            el_set (m_editline, EL_BIND, "-a", "^?", "lldb-delete-previous-char", NULL);
            
            // Escape is absorbed exiting edit mode, so re-register important sequences
            // without the prefix
            el_set (m_editline, EL_BIND, "-a", "[A", "lldb-previous-line", NULL);
            el_set (m_editline, EL_BIND, "-a", "[B", "lldb-next-line", NULL);
            el_set (m_editline, EL_BIND, "-a", "[\\^", "lldb-revert-line", NULL);
        }
    }
}

//------------------------------------------------------------------
// Editline public methods
//------------------------------------------------------------------

Editline *
Editline::InstanceFor (EditLine * editline)
{
    Editline * editor;
    el_get (editline, EL_CLIENTDATA, &editor);
    return editor;
}

Editline::Editline (const char * editline_name, FILE * input_file, FILE * output_file, FILE * error_file, bool color_prompts) :
    m_editor_status (EditorStatus::Complete),
    m_color_prompts(color_prompts),
    m_input_file (input_file),
    m_output_file (output_file),
    m_error_file (error_file),
    m_input_connection (fileno(input_file), false)
{
    // Get a shared history instance
    m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name;
    m_history_sp = EditlineHistory::GetHistory (m_editor_name);
}

Editline::~Editline()
{
    if (m_editline) 
    {
        // 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 = nullptr;
    }

    // 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();
}

void
Editline::SetPrompt (const char * prompt)
{
    m_set_prompt = prompt == nullptr ? "" : prompt;
}

void
Editline::SetContinuationPrompt (const char * continuation_prompt)
{
    m_set_continuation_prompt = continuation_prompt == nullptr ? "" : continuation_prompt;
}

void
Editline::TerminalSizeChanged()
{
    if (m_editline != nullptr) 
    {
        el_resize (m_editline);
        int columns;
        // Despite the man page claiming non-zero indicates success, it's actually zero
        if (el_get (m_editline, EL_GETTC, "co", &columns) == 0) 
        {
            m_terminal_width = columns;
            if (m_current_line_rows != -1) 
            {
                const LineInfoW * info = el_wline (m_editline);
                int lineLength = (int)((info->lastchar - info->buffer) + GetPromptWidth());
                m_current_line_rows = (lineLength / columns) + 1;
            }
        }
        else 
        {
            m_terminal_width = INT_MAX;
            m_current_line_rows = 1;
        }
    }
}

const char *
Editline::GetPrompt()
{
    return m_set_prompt.c_str();
}

uint32_t
Editline::GetCurrentLine()
{
    return m_current_line_index;
}

bool
Editline::Interrupt()
{
    bool result = true;
    Mutex::Locker locker(m_output_mutex);
    if (m_editor_status == EditorStatus::Editing) {
        fprintf(m_output_file, "^C\n");
        result = m_input_connection.InterruptRead();
    }
    m_editor_status = EditorStatus::Interrupted;
    return result;
}

bool
Editline::Cancel()
{
    bool result = true;
    Mutex::Locker locker(m_output_mutex);
    if (m_editor_status == EditorStatus::Editing) {
        MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);
        fprintf(m_output_file, ANSI_CLEAR_BELOW);
        result = m_input_connection.InterruptRead();
    }
    m_editor_status = EditorStatus::Interrupted;
    return result;
}

void
Editline::SetAutoCompleteCallback (CompleteCallbackType callback, void * baton)
{
    m_completion_callback = callback;
    m_completion_callback_baton = baton;
}

void
Editline::SetIsInputCompleteCallback (IsInputCompleteCallbackType callback, void * baton)
{
    m_is_input_complete_callback = callback;
    m_is_input_complete_callback_baton = baton;
}

bool
Editline::SetFixIndentationCallback (FixIndentationCallbackType callback,
                                     void * baton,
                                     const char * indent_chars)
{
    m_fix_indentation_callback = callback;
    m_fix_indentation_callback_baton = baton;
    m_fix_indentation_callback_chars = indent_chars;
    return false;
}

bool
Editline::GetLine (std::string &line, bool &interrupted)
{
    ConfigureEditor (false);
    m_input_lines = std::vector<EditLineStringType>();
    m_input_lines.insert (m_input_lines.begin(), EditLineConstString(""));
    
    Mutex::Locker locker(m_output_mutex);

    lldbassert(m_editor_status != EditorStatus::Editing);
    if (m_editor_status == EditorStatus::Interrupted)
    {
        m_editor_status = EditorStatus::Complete;
        interrupted = true;
        return true;
    }

    SetCurrentLine (0);
    m_in_history = false;
    m_editor_status = EditorStatus::Editing;
    m_revert_cursor_index = -1;

#ifdef USE_SETUPTERM_WORKAROUND
        setupterm((char *)0, fileno(m_output_file), (int *)0);
#endif

    int count;
    auto input = el_wgets (m_editline, &count);

    interrupted = m_editor_status == EditorStatus::Interrupted;
    if (!interrupted)
    {
        if (input == nullptr)
        {
            fprintf (m_output_file, "\n");
            m_editor_status = EditorStatus::EndOfInput;
        }
        else
        {
            m_history_sp->Enter (input);
#if LLDB_EDITLINE_USE_WCHAR
            line = m_utf8conv.to_bytes (SplitLines (input)[0]);
#else
            line = SplitLines (input)[0];
#endif
            m_editor_status = EditorStatus::Complete;
        }
    }
    return m_editor_status != EditorStatus::EndOfInput;
}

bool
Editline::GetLines (int first_line_number, StringList &lines, bool &interrupted)
{
    ConfigureEditor (true);
    
    // Print the initial input lines, then move the cursor back up to the start of input
    SetBaseLineNumber (first_line_number);
    m_input_lines = std::vector<EditLineStringType>();
    m_input_lines.insert (m_input_lines.begin(), EditLineConstString(""));
    
    Mutex::Locker locker(m_output_mutex);
    // Begin the line editing loop
    DisplayInput();
    SetCurrentLine (0);
    MoveCursor (CursorLocation::BlockEnd, CursorLocation::BlockStart);
    m_editor_status = EditorStatus::Editing;
    m_in_history = false;

    m_revert_cursor_index = -1;
    while (m_editor_status == EditorStatus::Editing)
    {
#ifdef USE_SETUPTERM_WORKAROUND
        setupterm((char *)0, fileno(m_output_file), (int *)0);
#endif
        int count;
        m_current_line_rows = -1;
        el_wpush (m_editline, EditLineConstString("\x1b[^")); // Revert to the existing line content
        el_wgets (m_editline, &count);
    }
    
    interrupted = m_editor_status == EditorStatus::Interrupted;
    if (!interrupted)
    {
        // Save the completed entry in history before returning
        m_history_sp->Enter (CombineLines (m_input_lines).c_str());

        lines = GetInputAsStringList();
    }
    return m_editor_status != EditorStatus::EndOfInput;
}

void
Editline::PrintAsync (Stream *stream, const char *s, size_t len)
{
    Mutex::Locker locker(m_output_mutex);
    if (m_editor_status == EditorStatus::Editing)
    {
        MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);
        fprintf(m_output_file, ANSI_CLEAR_BELOW);
    }
    stream->Write (s, len);
    stream->Flush();
    if (m_editor_status == EditorStatus::Editing)
    {
        DisplayInput();
        MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);
    }
}
