Files
clang-p2996/lldb/source/Host/common/Editline.cpp
Pavel Labath 4446487d71 Improve LLDB prompt handling
Summary:
There is an issue in lldb where the command prompt can appear at the wrong time. The partial fix
we have in for this is not working all the time and is introducing unnecessary delays. This
change does:
- Change Process:SyncIOHandler to use integer start id's for synchronization to avoid it being
  confused by quick start-stop cycles. I picked this up from a suggested patch by Greg to
  lldb-dev.
- coordinates printing of asynchronous text with the iohandlers. This is also based on a
  (different) Greg's patch, but I have added stronger synchronization to it to avoid races.

Together, these changes solve the prompt problem for me on linux (both with and without libedit).
I think they should behave similarly on Mac and FreeBSD and I think they will not make matters
worse for windows.

Test Plan: Prompt comes out alright. All tests still pass on linux.

Reviewers: clayborg, emaste, zturner

Subscribers: lldb-commits

Differential Revision: http://reviews.llvm.org/D9823

llvm-svn: 238313
2015-05-27 12:40:32 +00:00

1428 lines
48 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 <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())
{
std::string parent_path = FileSpec ("~/.lldb", true).GetPath();
char history_path[PATH_MAX];
if (FileSystem::MakeDirectory(parent_path.c_str(), 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 = std::move (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 by hand prior to correction
EditLineCharType inserted[] = { (EditLineCharType)ch, 0 };
el_winsertstr (m_editline, inserted);
SaveEditedLine();
StringList lines = GetInputAsStringList (m_current_line_index + 1);
// Determine the cursor position
LineInfoW * info = const_cast<LineInfoW *>(el_wline (m_editline));
int cursor_position = info->cursor - info->buffer;
int indent_correction = m_fix_indentation_callback (this, lines, cursor_position, m_fix_indentation_callback_baton);
// Adjust the input buffer to correct indentation
if (indent_correction > 0)
{
info->cursor = info->buffer;
el_winsertstr (m_editline, EditLineStringType (indent_correction, EditLineCharType(' ')).c_str());
}
else if (indent_correction < 0)
{
info->cursor = info->buffer - indent_correction;
el_wdeletestr (m_editline, -indent_correction);
}
info->cursor = info->buffer + cursor_position + indent_correction;
return CC_REFRESH;
}
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);
}
}