mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-26 23:56:06 +00:00

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
672 lines
20 KiB
C++
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()
|
|
}
|