Greg Clayton 44d937820b Merging the iohandler branch back into main.
The many many benefits include:
1 - Input/Output/Error streams are now handled as real streams not a push style input
2 - auto completion in python embedded interpreter
3 - multi-line input for "script" and "expression" commands now allow you to edit previous/next lines using up and down arrow keys and this makes multi-line input actually a viable thing to use
4 - it is now possible to use curses to drive LLDB (please try the "gui" command)

We will need to deal with and fix any buildbot failures and tests and arise now that input/output and error are correctly hooked up in all cases.

llvm-svn: 200263
2014-01-27 23:43:24 +00:00

672 lines
20 KiB
C++

//===-- 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 <limits.h>
using namespace lldb;
using namespace lldb_private;
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
FILE *fin,
FILE *fout,
FILE *ferr) :
m_editline (NULL),
m_history (NULL),
m_history_event (),
m_program (),
m_prompt (),
m_lines_prompt (),
m_getc_buffer (),
m_getc_mutex (Mutex::eMutexTypeNormal),
m_getc_cond (),
// m_gets_mutex (Mutex::eMutexTypeNormal),
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_lines_curr_line (0),
m_lines_max_line (0),
m_prompt_with_line_numbers (false),
m_getting_line (false),
m_got_eof (false),
m_interrupted (false)
{
if (prog && prog[0])
{
m_program = prog;
m_editline = ::el_init(prog, fin, fout, ferr);
m_history = ::history_init();
}
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);
::el_set (m_editline, EL_PROMPT_ESC, GetPromptCallback, k_prompt_escape_char);
::el_set (m_editline, EL_EDITOR, "emacs");
if (m_history)
{
::el_set (m_editline, EL_HIST, history, m_history);
}
::el_set (m_editline, EL_ADDFN, "lldb-complete", "Editline completion function", Editline::CallbackComplete);
::el_set (m_editline, EL_ADDFN, "lldb-edit-prev-line", "Editline edit prev line", Editline::CallbackEditPrevLine);
::el_set (m_editline, EL_ADDFN, "lldb-edit-next-line", "Editline edit next line", Editline::CallbackEditNextLine);
::el_set (m_editline, EL_BIND, "^r", "em-inc-search-prev", NULL); // Cycle through backwards search, entering string
::el_set (m_editline, EL_BIND, "^w", "ed-delete-prev-word", NULL); // Delete previous word, behave like bash does.
::el_set (m_editline, EL_BIND, "\033[3~", "ed-delete-next-char", NULL); // Fix the delete key.
::el_set (m_editline, EL_BIND, "\t", "lldb-complete", NULL); // Bind TAB to be autocompelte
// Source $PWD/.editrc then $HOME/.editrc
::el_source (m_editline, NULL);
if (m_history)
{
::history (m_history, &m_history_event, H_SETSIZE, 800);
::history (m_history, &m_history_event, H_SETUNIQUE, 1);
}
// Always read through our callback function so we don't read
// stuff we aren't supposed to. This also stops the extra echoing
// that can happen when you have more input than editline can handle
// at once.
SetGetCharCallback(GetCharFromInputFileCallback);
LoadHistory();
}
Editline::~Editline()
{
SaveHistory();
if (m_history)
{
::history_end (m_history);
m_history = NULL;
}
// 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);
}
FileSpec
Editline::GetHistoryFile()
{
char history_path[PATH_MAX];
::snprintf (history_path, sizeof(history_path), "~/.%s-history", m_program.c_str());
return FileSpec(history_path, true);
}
bool
Editline::LoadHistory ()
{
if (m_history)
{
FileSpec history_file(GetHistoryFile());
if (history_file.Exists())
::history (m_history, &m_history_event, H_LOAD, history_file.GetPath().c_str());
return true;
}
return false;
}
bool
Editline::SaveHistory ()
{
if (m_history)
{
std::string history_path = GetHistoryFile().GetPath();
::history (m_history, &m_history_event, H_SAVE, history_path.c_str());
return true;
}
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;
const char *line_cstr = NULL;
// Call el_gets to prompt the user and read the user's input.
// {
// // Make sure we know when we are in el_gets() by using a mutex
// Mutex::Locker locker (m_gets_mutex);
line_cstr = ::el_gets (m_editline, &line_len);
// }
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
while (line_len > 0 &&
(line_cstr[line_len - 1] == '\n' ||
line_cstr[line_len - 1] == '\r'))
--line_len;
if (line_len > 0)
{
// We didn't strip the newlines, we just adjusted the length, and
// we want to add the history item with the newlines
if (m_history)
::history (m_history, &m_history_event, H_ENTER, line_cstr);
// Copy the part of the c string that we want (removing the newline chars)
line.assign(line_cstr, line_len);
}
}
}
else
{
error.SetErrorString("the EditLine instance has been deleted");
}
return error;
}
Error
Editline::GetLine(std::string &line)
{
Error error;
line.clear();
// Set arrow key bindings for up and down arrows for single line
// mode where up and down arrows do prev/next history
::el_set (m_editline, EL_BIND, "^[[A", "ed-prev-history", NULL); // Map up arrow
::el_set (m_editline, EL_BIND, "^[[B", "ed-next-history", NULL); // Map down arrow
m_interrupted = false;
if (!m_got_eof)
{
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;
}
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, std::string (bytes, len).c_str());
return len;
}
return 0;
}
Error
Editline::GetLines(const std::string &end_line, StringList &lines)
{
Error error;
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
::el_set (m_editline, EL_BIND, "^[[A", "lldb-edit-prev-line", NULL); // Map up arrow
::el_set (m_editline, EL_BIND, "^[[B", "lldb-edit-next-line", NULL); // Map down arrow
::el_set (m_editline, EL_BIND, "^b", "ed-prev-history", NULL);
::el_set (m_editline, EL_BIND, "^n", "ed-next-history", NULL);
m_interrupted = false;
LineStatus line_status = LineStatus::Success;
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
{
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, 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, 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, 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)
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 "";
}
size_t
Editline::SetInputBuffer (const char *c, size_t len)
{
if (c && len > 0)
{
Mutex::Locker locker(m_getc_mutex);
SetGetCharCallback(GetCharInputBufferCallback);
m_getc_buffer.append(c, len);
m_getc_cond.Broadcast();
}
return len;
}
int
Editline::GetChar (char *c)
{
Mutex::Locker locker(m_getc_mutex);
if (m_getc_buffer.empty())
m_getc_cond.Wait(m_getc_mutex);
if (m_getc_buffer.empty())
return 0;
*c = m_getc_buffer[0];
m_getc_buffer.erase(0,1);
return 1;
}
int
Editline::GetCharInputBufferCallback (EditLine *e, char *c)
{
Editline *editline = GetClientData (e);
if (editline)
return editline->GetChar(c);
return 0;
}
int
Editline::GetCharFromInputFileCallback (EditLine *e, char *c)
{
Editline *editline = GetClientData (e);
if (editline && editline->m_got_eof == false)
{
char ch = ::fgetc(editline->GetInputFile());
if (ch == '\x04' || ch == EOF)
{
editline->m_got_eof = true;
}
else
{
*c = ch;
return 1;
}
}
return 0;
}
void
Editline::Hide ()
{
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()
{
::el_set (m_editline, EL_REFRESH);
}
void
Editline::Interrupt ()
{
m_interrupted = true;
if (m_getting_line || m_lines_curr_line > 0)
el_insertstr(m_editline, "\n"); // True to force the line to complete itself so we get exit from el_gets()
}