forked from OSchip/llvm-project
1378 lines
46 KiB
C++
1378 lines
46 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"
|
|
|
|
using namespace lldb_private;
|
|
using namespace lldb_private::line_editor;
|
|
|
|
// 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;
|
|
m_editor_getting_char = true;
|
|
int read_count = m_input_connection.Read(&ch, 1, UINT32_MAX, status, NULL);
|
|
m_editor_getting_char = false;
|
|
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(*c != EOF)
|
|
return 1;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
switch (status)
|
|
{
|
|
case lldb::eConnectionStatusInterrupted:
|
|
m_editor_status = EditorStatus::Interrupted;
|
|
printf ("^C\n");
|
|
return 0;
|
|
|
|
case lldb::eConnectionStatusSuccess: // Success
|
|
break;
|
|
|
|
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 = (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 = (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 = (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 = (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;
|
|
}
|
|
|
|
void
|
|
Editline::Hide()
|
|
{
|
|
// Make sure we're at a stable location waiting for input
|
|
while (m_editor_status == EditorStatus::Editing && !m_editor_getting_char)
|
|
{
|
|
usleep(100000);
|
|
}
|
|
|
|
// Clear the existing input
|
|
if (m_editor_status == EditorStatus::Editing)
|
|
{
|
|
MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);
|
|
fprintf(m_output_file, ANSI_CLEAR_BELOW);
|
|
}
|
|
}
|
|
|
|
void
|
|
Editline::Refresh()
|
|
{
|
|
if (m_editor_status == EditorStatus::Editing)
|
|
{
|
|
DisplayInput();
|
|
MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);
|
|
}
|
|
}
|
|
|
|
bool
|
|
Editline::Interrupt()
|
|
{
|
|
if (m_editor_status == EditorStatus::Editing)
|
|
{
|
|
return m_input_connection.InterruptRead();
|
|
}
|
|
return false; // Interrupt not handled as we weren't getting a line or lines
|
|
}
|
|
|
|
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(""));
|
|
|
|
SetCurrentLine (0);
|
|
m_in_history = false;
|
|
m_editor_status = EditorStatus::Editing;
|
|
m_editor_getting_char = false;
|
|
m_revert_cursor_index = -1;
|
|
|
|
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(""));
|
|
|
|
// Begin the line editing loop
|
|
DisplayInput();
|
|
SetCurrentLine (0);
|
|
MoveCursor (CursorLocation::BlockEnd, CursorLocation::BlockStart);
|
|
m_editor_status = EditorStatus::Editing;
|
|
m_editor_getting_char = false;
|
|
m_in_history = false;
|
|
|
|
m_revert_cursor_index = -1;
|
|
while (m_editor_status == EditorStatus::Editing)
|
|
{
|
|
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;
|
|
}
|