[Lldb-commits] [lldb] r222163 - Complete rewrite of interactive editing support for single- and multi-line input.
Kate Stone
katherine.stone at apple.com
Mon Nov 17 11:07:00 PST 2014
Author: kate
Date: Mon Nov 17 13:06:59 2014
New Revision: 222163
URL: http://llvm.org/viewvc/llvm-project?rev=222163&view=rev
Log:
Complete rewrite of interactive editing support for single- and multi-line input.
Improvements include:
* Use of libedit's wide character support, which is imperfect but a distinct improvement over ASCII-only
* Fallback for ASCII editing path
* Support for a "faint" prompt clearly distinguished from input
* Breaking lines and insert new lines in the middle of a batch by simply pressing return
* Joining lines with forward and backward character deletion
* Detection of paste to suppress automatic formatting and statement completion tests
* Correctly reformatting when lines grow or shrink to occupy different numbers of rows
* Saving multi-line history, and correctly preserving the "tip" of history during editing
* Displaying visible ^C and ^D indications when interrupting input or sending EOF
* Fledgling VI support for multi-line editing
* General correctness and reliability improvements
Modified:
lldb/trunk/include/lldb/Core/IOHandler.h
lldb/trunk/include/lldb/Host/Editline.h
lldb/trunk/include/lldb/Interpreter/CommandInterpreter.h
lldb/trunk/source/Commands/CommandObjectCommands.cpp
lldb/trunk/source/Commands/CommandObjectExpression.cpp
lldb/trunk/source/Core/IOHandler.cpp
lldb/trunk/source/Host/common/Editline.cpp
lldb/trunk/source/Interpreter/CommandInterpreter.cpp
lldb/trunk/source/Interpreter/ScriptInterpreterPython.cpp
lldb/trunk/source/Target/Process.cpp
Modified: lldb/trunk/include/lldb/Core/IOHandler.h
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Core/IOHandler.h?rev=222163&r1=222162&r2=222163&view=diff
==============================================================================
--- lldb/trunk/include/lldb/Core/IOHandler.h (original)
+++ lldb/trunk/include/lldb/Core/IOHandler.h Mon Nov 17 13:06:59 2014
@@ -19,9 +19,11 @@
#include "lldb/Core/ConstString.h"
#include "lldb/Core/Error.h"
#include "lldb/Core/Flags.h"
+#include "lldb/Core/Stream.h"
#include "lldb/Core/StringList.h"
#include "lldb/Core/ValueObjectList.h"
#include "lldb/Host/Mutex.h"
+#include "lldb/Host/Predicate.h"
namespace curses
{
@@ -34,9 +36,23 @@ namespace lldb_private {
class IOHandler
{
public:
- IOHandler (Debugger &debugger);
+ enum class Type {
+ CommandInterpreter,
+ CommandList,
+ Confirm,
+ Curses,
+ Expression,
+ ProcessIO,
+ PythonInterpreter,
+ PythonCode,
+ Other
+ };
+
+ IOHandler (Debugger &debugger,
+ IOHandler::Type type);
IOHandler (Debugger &debugger,
+ IOHandler::Type type,
const lldb::StreamFileSP &input_sp,
const lldb::StreamFileSP &output_sp,
const lldb::StreamFileSP &error_sp,
@@ -97,6 +113,12 @@ namespace lldb_private {
return m_done;
}
+ Type
+ GetType () const
+ {
+ return m_type;
+ }
+
virtual void
Activate ()
{
@@ -128,7 +150,19 @@ namespace lldb_private {
{
return ConstString();
}
+
+ virtual const char *
+ GetCommandPrefix ()
+ {
+ return NULL;
+ }
+ virtual const char *
+ GetHelpPrologue()
+ {
+ return NULL;
+ }
+
int
GetInputFD();
@@ -206,13 +240,21 @@ namespace lldb_private {
//------------------------------------------------------------------
bool
GetIsRealTerminal ();
-
+
+ void
+ SetPopped (bool b);
+
+ void
+ WaitForPop ();
+
protected:
Debugger &m_debugger;
lldb::StreamFileSP m_input_sp;
lldb::StreamFileSP m_output_sp;
lldb::StreamFileSP m_error_sp;
+ Predicate<bool> m_popped;
Flags m_flags;
+ Type m_type;
void *m_user_data;
bool m_done;
bool m_active;
@@ -254,7 +296,12 @@ namespace lldb_private {
IOHandlerActivated (IOHandler &io_handler)
{
}
-
+
+ virtual void
+ IOHandlerDeactivated (IOHandler &io_handler)
+ {
+ }
+
virtual int
IOHandlerComplete (IOHandler &io_handler,
const char *current_line,
@@ -264,6 +311,44 @@ namespace lldb_private {
int max_matches,
StringList &matches);
+ virtual const char *
+ IOHandlerGetFixIndentationCharacters ()
+ {
+ return NULL;
+ }
+
+ //------------------------------------------------------------------
+ /// Called when a new line is created or one of an identifed set of
+ /// indentation characters is typed.
+ ///
+ /// This function determines how much indentation should be added
+ /// or removed to match the recommended amount for the final line.
+ ///
+ /// @param[in] io_handler
+ /// The IOHandler that responsible for input.
+ ///
+ /// @param[in] lines
+ /// The current input up to the line to be corrected. Lines
+ /// following the line containing the cursor are not included.
+ ///
+ /// @param[in] cursor_position
+ /// The number of characters preceeding the cursor on the final
+ /// line at the time.
+ ///
+ /// @return
+ /// Returns an integer describing the number of spaces needed
+ /// to correct the indentation level. Positive values indicate
+ /// that spaces should be added, while negative values represent
+ /// spaces that should be removed.
+ //------------------------------------------------------------------
+ virtual int
+ IOHandlerFixIndentation (IOHandler &io_handler,
+ const StringList &lines,
+ int cursor_position)
+ {
+ return 0;
+ }
+
//------------------------------------------------------------------
/// Called when a line or lines have been retrieved.
///
@@ -275,40 +360,55 @@ namespace lldb_private {
//------------------------------------------------------------------
virtual void
IOHandlerInputComplete (IOHandler &io_handler, std::string &data) = 0;
-
+
+ virtual void
+ IOHandlerInputInterrupted (IOHandler &io_handler, std::string &data)
+ {
+ }
+
//------------------------------------------------------------------
- /// Called when a line in \a lines has been updated when doing
- /// multi-line input.
+ /// Called to determine whether typing enter after the last line in
+ /// \a lines should end input. This function will not be called on
+ /// IOHandler objects that are getting single lines.
+ /// @param[in] io_handler
+ /// The IOHandler that responsible for updating the lines.
+ ///
+ /// @param[in] lines
+ /// The current multi-line content. May be altered to provide
+ /// alternative input when complete.
///
/// @return
- /// Return an enumeration to indicate the status of the current
- /// line:
- /// Success - The line is good and should be added to the
- /// multiple lines
- /// Error - There is an error with the current line and it
- /// need to be re-edited before it is acceptable
- /// Done - The lines collection is complete and ready to be
- /// returned.
- //------------------------------------------------------------------
- virtual LineStatus
- IOHandlerLinesUpdated (IOHandler &io_handler,
- StringList &lines,
- uint32_t line_idx,
- Error &error)
- {
- return LineStatus::Done; // Stop getting lines on the first line that is updated
- // subclasses should do something more intelligent here.
- // This function will not be called on IOHandler objects
- // that are getting single lines.
+ /// Return an boolean to indicate whether input is complete,
+ /// true indicates that no additional input is necessary, while
+ /// false indicates that more input is required.
+ //------------------------------------------------------------------
+ virtual bool
+ IOHandlerIsInputComplete (IOHandler &io_handler,
+ StringList &lines)
+ {
+ // Impose no requirements for input to be considered
+ // complete. subclasses should do something more intelligent.
+ return true;
}
-
virtual ConstString
IOHandlerGetControlSequence (char ch)
{
return ConstString();
}
+ virtual const char *
+ IOHandlerGetCommandPrefix ()
+ {
+ return NULL;
+ }
+
+ virtual const char *
+ IOHandlerGetHelpPrologue ()
+ {
+ return NULL;
+ }
+
//------------------------------------------------------------------
// Intercept the IOHandler::Interrupt() calls and do something.
//
@@ -356,30 +456,21 @@ namespace lldb_private {
return ConstString();
}
- virtual LineStatus
- IOHandlerLinesUpdated (IOHandler &io_handler,
- StringList &lines,
- uint32_t line_idx,
- Error &error)
+ virtual bool
+ IOHandlerIsInputComplete (IOHandler &io_handler,
+ StringList &lines)
{
- if (line_idx == UINT32_MAX)
+ // Determine whether the end of input signal has been entered
+ const size_t num_lines = lines.GetSize();
+ if (num_lines > 0 && lines[num_lines - 1] == m_end_line)
{
- // Remove the last empty line from "lines" so it doesn't appear
- // in our final expression and return true to indicate we are done
+ // Remove the terminal line from "lines" so it doesn't appear in
+ // the resulting input and return true to indicate we are done
// getting lines
lines.PopBack();
- return LineStatus::Done;
+ return true;
}
- else if (line_idx + 1 == lines.GetSize())
- {
- // The last line was edited, if this line is empty, then we are done
- // getting our multiple lines.
- if (lines[line_idx] == m_end_line)
- {
- return LineStatus::Done;
- }
- }
- return LineStatus::Success;
+ return false;
}
protected:
const std::string m_end_line;
@@ -390,20 +481,26 @@ namespace lldb_private {
{
public:
IOHandlerEditline (Debugger &debugger,
+ IOHandler::Type type,
const char *editline_name, // Used for saving history files
const char *prompt,
+ const char *continuation_prompt,
bool multi_line,
+ bool color_prompts,
uint32_t line_number_start, // If non-zero show line numbers starting at 'line_number_start'
IOHandlerDelegate &delegate);
IOHandlerEditline (Debugger &debugger,
+ IOHandler::Type type,
const lldb::StreamFileSP &input_sp,
const lldb::StreamFileSP &output_sp,
const lldb::StreamFileSP &error_sp,
uint32_t flags,
const char *editline_name, // Used for saving history files
const char *prompt,
+ const char *continuation_prompt,
bool multi_line,
+ bool color_prompts,
uint32_t line_number_start, // If non-zero show line numbers starting at 'line_number_start'
IOHandlerDelegate &delegate);
@@ -429,11 +526,10 @@ namespace lldb_private {
GotEOF();
virtual void
- Activate ()
- {
- IOHandler::Activate();
- m_delegate.IOHandlerActivated(*this);
- }
+ Activate ();
+
+ virtual void
+ Deactivate ();
virtual ConstString
GetControlSequence (char ch)
@@ -442,11 +538,29 @@ namespace lldb_private {
}
virtual const char *
+ GetCommandPrefix ()
+ {
+ return m_delegate.IOHandlerGetCommandPrefix ();
+ }
+
+ virtual const char *
+ GetHelpPrologue ()
+ {
+ return m_delegate.IOHandlerGetHelpPrologue ();
+ }
+
+ virtual const char *
GetPrompt ();
virtual bool
SetPrompt (const char *prompt);
-
+
+ const char *
+ GetContinuationPrompt ();
+
+ void
+ SetContinuationPrompt (const char *prompt);
+
bool
GetLine (std::string &line, bool &interrupted);
@@ -456,15 +570,40 @@ namespace lldb_private {
void
SetBaseLineNumber (uint32_t line);
+ bool
+ GetInterruptExits ()
+ {
+ return m_interrupt_exits;
+ }
+
+ void
+ SetInterruptExits (bool b)
+ {
+ m_interrupt_exits = b;
+ }
+
+ const StringList *
+ GetCurrentLines () const
+ {
+ return m_current_lines_ptr;
+ }
+
+ uint32_t
+ GetCurrentLineIndex () const;
+
private:
#ifndef LLDB_DISABLE_LIBEDIT
- static LineStatus
- LineCompletedCallback (Editline *editline,
- StringList &lines,
- uint32_t line_idx,
- Error &error,
- void *baton);
-
+ static bool
+ IsInputCompleteCallback (Editline *editline,
+ StringList &lines,
+ void *baton);
+
+ static int
+ FixIndentationCallback (Editline *editline,
+ const StringList &lines,
+ int cursor_position,
+ void *baton);
+
static int AutoCompleteCallback (const char *current_line,
const char *cursor,
const char *last_char,
@@ -480,8 +619,13 @@ namespace lldb_private {
#endif
IOHandlerDelegate &m_delegate;
std::string m_prompt;
+ std::string m_continuation_prompt;
+ StringList *m_current_lines_ptr;
uint32_t m_base_line_number; // If non-zero, then show line numbers in prompt
- bool m_multi_line;
+ uint32_t m_curr_line_idx;
+ bool m_multi_line;
+ bool m_color_prompts;
+ bool m_interrupt_exits;
};
class IOHandlerConfirm :
@@ -611,7 +755,8 @@ namespace lldb_private {
if (sp)
{
Mutex::Locker locker (m_mutex);
- m_stack.push (sp);
+ sp->SetPopped (false);
+ m_stack.push_back (sp);
// Set m_top the non-locking IsTop() call
m_top = sp.get();
}
@@ -631,7 +776,7 @@ namespace lldb_private {
{
Mutex::Locker locker (m_mutex);
if (!m_stack.empty())
- sp = m_stack.top();
+ sp = m_stack.back();
}
return sp;
}
@@ -641,12 +786,16 @@ namespace lldb_private {
{
Mutex::Locker locker (m_mutex);
if (!m_stack.empty())
- m_stack.pop();
+ {
+ lldb::IOHandlerSP sp (m_stack.back());
+ m_stack.pop_back();
+ sp->SetPopped (true);
+ }
// Set m_top the non-locking IsTop() call
if (m_stack.empty())
m_top = NULL;
else
- m_top = m_stack.top().get();
+ m_top = m_stack.back().get();
}
Mutex &
@@ -661,6 +810,19 @@ namespace lldb_private {
return m_top == io_handler_sp.get();
}
+ bool
+ CheckTopIOHandlerTypes (IOHandler::Type top_type, IOHandler::Type second_top_type)
+ {
+ Mutex::Locker locker (m_mutex);
+ const size_t num_io_handlers = m_stack.size();
+ if (num_io_handlers >= 2 &&
+ m_stack[num_io_handlers-1]->GetType() == top_type &&
+ m_stack[num_io_handlers-2]->GetType() == second_top_type)
+ {
+ return true;
+ }
+ return false;
+ }
ConstString
GetTopIOHandlerControlSequence (char ch)
{
@@ -669,9 +831,26 @@ namespace lldb_private {
return ConstString();
}
- protected:
+ const char *
+ GetTopIOHandlerCommandPrefix()
+ {
+ if (m_top)
+ return m_top->GetCommandPrefix();
+ return NULL;
+ }
+
+ const char *
+ GetTopIOHandlerHelpPrologue()
+ {
+ if (m_top)
+ return m_top->GetHelpPrologue();
+ return NULL;
+ }
+
+ protected:
- std::stack<lldb::IOHandlerSP> m_stack;
+ typedef std::vector<lldb::IOHandlerSP> collection;
+ collection m_stack;
mutable Mutex m_mutex;
IOHandler *m_top;
Modified: lldb/trunk/include/lldb/Host/Editline.h
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Host/Editline.h?rev=222163&r1=222162&r2=222163&view=diff
==============================================================================
--- lldb/trunk/include/lldb/Host/Editline.h (original)
+++ lldb/trunk/include/lldb/Host/Editline.h Mon Nov 17 13:06:59 2014
@@ -7,13 +7,40 @@
//
//===----------------------------------------------------------------------===//
+//TODO: wire up window size changes
+
+// If we ever get a private copy of libedit, there are a number of defects that would be nice to fix;
+// a) Sometimes text just disappears while editing. In an 80-column editor paste the following text, without
+// the quotes:
+// "This is a test of the input system missing Hello, World! Do you disappear when it gets to a particular length?"
+// Now press ^A to move to the start and type 3 characters, and you'll see a good amount of the text will
+// disappear. It's still in the buffer, just invisible.
+// b) The prompt printing logic for dealing with ANSI formatting characters is broken, which is why we're
+// working around it here.
+// c) When resizing the terminal window, if the cursor moves between rows libedit will get confused.
+// d) The incremental search uses escape to cancel input, so it's confused by ANSI sequences starting with escape.
+// e) Emoji support is fairly terrible, presumably it doesn't understand composed characters?
+
#ifndef liblldb_Editline_h_
#define liblldb_Editline_h_
#if defined(__cplusplus)
+#include <sstream>
+#include <vector>
+
+// components needed to handle wide characters ( <codecvt>, codecvt_utf8, libedit built with '--enable-widec' )
+// are not consistenly available on non-OSX platforms. The wchar_t versions of libedit functions will only be
+// used in cases where this is true. This is a compile time dependecy, for now selected per target Platform
+#if defined (__APPLE__)
+#define LLDB_EDITLINE_USE_WCHAR 1
+#include <codecvt>
+#else
+#define LLDB_EDITLINE_USE_WCHAR 0
+#endif
+
#include "lldb/lldb-private.h"
+#include "lldb/Host/ConnectionFileDescriptor.h"
-#include <stdio.h>
#if defined(_WIN32)
#include "lldb/Host/windows/editlinewin.h"
#else
@@ -32,179 +59,308 @@
#include "lldb/Host/Predicate.h"
namespace lldb_private {
+ namespace line_editor {
-//----------------------------------------------------------------------
-/// @class Editline Editline.h "lldb/Host/Editline.h"
-/// @brief A class that encapsulates editline functionality.
-//----------------------------------------------------------------------
-class EditlineHistory;
-
-typedef std::shared_ptr<EditlineHistory> EditlineHistorySP;
-
-class Editline
-{
-public:
- typedef LineStatus (*LineCompletedCallbackType) (
- Editline *editline,
- StringList &lines,
- uint32_t line_idx,
- Error &error,
- void *baton);
-
- typedef int (*CompleteCallbackType) (
- const char *current_line,
- const char *cursor,
- const char *last_char,
- int skip_first_n_matches,
- int max_matches,
- StringList &matches,
- void *baton);
-
- typedef int (*GetCharCallbackType) (
- ::EditLine *,
- char *c);
-
- Editline(const char *prog, // Used for the history file and for editrc program name
- const char *prompt,
- bool configure_for_multiline,
- FILE *fin,
- FILE *fout,
- FILE *ferr);
-
- ~Editline();
-
- Error
- GetLine (std::string &line,
- bool &interrupted);
-
- Error
- GetLines (const std::string &end_line,
- StringList &lines,
- bool &interrupted);
-
- bool
- LoadHistory ();
-
- bool
- SaveHistory ();
-
- FILE *
- GetInputFile ();
-
- FILE *
- GetOutputFile ();
-
- FILE *
- GetErrorFile ();
-
- bool
- GettingLine () const
- {
- return m_getting_line;
- }
-
- void
- Hide ();
-
- void
- Refresh();
-
- bool
- Interrupt ();
-
- void
- SetAutoCompleteCallback (CompleteCallbackType callback,
- void *baton)
- {
- m_completion_callback = callback;
- m_completion_callback_baton = baton;
- }
-
- void
- SetLineCompleteCallback (LineCompletedCallbackType callback,
- void *baton)
- {
- m_line_complete_callback = callback;
- m_line_complete_callback_baton = baton;
- }
-
- size_t
- Push (const char *bytes, size_t len);
-
- static int
- GetCharFromInputFileCallback (::EditLine *e, char *c);
+ // type alias's to help manage 8 bit and wide character versions of libedit
+#if LLDB_EDITLINE_USE_WCHAR
+ using EditLineStringType = std::wstring;
+ using EditLineStringStreamType = std::wstringstream;
+ using EditLineCharType = wchar_t;
+#else
+ using EditLineStringType=std::string;
+ using EditLineStringStreamType = std::stringstream;
+ using EditLineCharType = char;
+#endif
- void
- SetGetCharCallback (GetCharCallbackType callback);
-
- const char *
- GetPrompt();
-
- void
- SetPrompt (const char *p);
-
- void
- ShowLineNumbers (bool enable, uint32_t line_offset)
- {
- m_prompt_with_line_numbers = enable;
- m_line_offset = line_offset;
+ typedef int (* EditlineGetCharCallbackType)(::EditLine * editline, EditLineCharType * c);
+ typedef unsigned char (* EditlineCommandCallbackType)(::EditLine * editline, int ch);
+ typedef const char * (* EditlinePromptCallbackType)(::EditLine * editline);
+
+ class EditlineHistory;
+
+ typedef std::shared_ptr<EditlineHistory> EditlineHistorySP;
+
+ typedef bool (* IsInputCompleteCallbackType) (
+ Editline * editline,
+ StringList & lines,
+ void * baton);
+
+ typedef int (* FixIndentationCallbackType) (
+ Editline * editline,
+ const StringList & lines,
+ int cursor_position,
+ void * baton);
+
+ typedef int (* CompleteCallbackType) (
+ const char * current_line,
+ const char * cursor,
+ const char * last_char,
+ int skip_first_n_matches,
+ int max_matches,
+ StringList & matches,
+ void * baton);
+
+ /// Status used to decide when and how to start editing another line in multi-line sessions
+ enum class EditorStatus
+ {
+
+ /// The default state proceeds to edit the current line
+ Editing,
+
+ /// Editing complete, returns the complete set of edited lines
+ Complete,
+
+ /// End of input reported
+ EndOfInput,
+
+ /// Editing interrupted
+ Interrupted
+ };
+
+ /// Established locations that can be easily moved among with MoveCursor
+ enum class CursorLocation
+ {
+ /// The start of the first line in a multi-line edit session
+ BlockStart,
+
+ /// The start of the current line in a multi-line edit session
+ EditingPrompt,
+
+ /// The location of the cursor on the current line in a multi-line edit session
+ EditingCursor,
+
+ /// The location immediately after the last character in a multi-line edit session
+ BlockEnd
+ };
}
-private:
-
- Error
- PrivateGetLine(std::string &line);
-
- unsigned char
- HandleCompletion (int ch);
-
- static unsigned char
- CallbackEditPrevLine (::EditLine *e, int ch);
-
- static unsigned char
- CallbackEditNextLine (::EditLine *e, int ch);
-
- static unsigned char
- CallbackComplete (::EditLine *e, int ch);
-
- static const char *
- GetPromptCallback (::EditLine *e);
-
- static Editline *
- GetClientData (::EditLine *e);
+ using namespace line_editor;
- static FILE *
- GetFilePointer (::EditLine *e, int fd);
-
- enum class Command
+ /// Instances of Editline provide an abstraction over libedit's EditLine facility. Both
+ /// single- and multi-line editing are supported.
+ class Editline
{
- None = 0,
- EditPrevLine,
- EditNextLine,
+ public:
+ Editline (const char * editor_name, FILE * input_file, FILE * output_file, FILE * error_file, bool color_prompts);
+
+ ~Editline();
+
+ /// Uses the user data storage of EditLine to retrieve an associated instance of Editline.
+ static Editline *
+ InstanceFor (::EditLine * editline);
+
+ /// Sets a string to be used as a prompt, or combined with a line number to form a prompt.
+ void
+ SetPrompt (const char * prompt);
+
+ /// Sets an alternate string to be used as a prompt for the second line and beyond in multi-line
+ /// editing scenarios.
+ void
+ SetContinuationPrompt (const char * continuation_prompt);
+
+ /// Required to update the width of the terminal registered for I/O. It is critical that this
+ /// be correct at all times.
+ void
+ TerminalSizeChanged();
+
+ /// Returns the prompt established by SetPrompt()
+ const char *
+ GetPrompt();
+
+ /// Returns the index of the line currently being edited
+ uint32_t
+ GetCurrentLine();
+
+ /// Hides the current input session in preparation for output
+ void
+ Hide();
+
+ /// Prepare to return to editing after a call to Hide()
+ void
+ Refresh();
+
+ /// Interrupt the current edit as if ^C was pressed
+ bool
+ Interrupt();
+
+ /// Register a callback for the tab key
+ void
+ SetAutoCompleteCallback (CompleteCallbackType callback, void * baton);
+
+ /// Register a callback for testing whether multi-line input is complete
+ void
+ SetIsInputCompleteCallback (IsInputCompleteCallbackType callback, void * baton);
+
+ /// Register a callback for determining the appropriate indentation for a line
+ /// when creating a newline. An optional set of insertable characters can also
+ /// trigger the callback.
+ bool
+ SetFixIndentationCallback (FixIndentationCallbackType callback,
+ void * baton,
+ const char * indent_chars);
+
+ /// Prompts for and reads a single line of user input.
+ bool
+ GetLine (std::string &line, bool &interrupted);
+
+ /// Prompts for and reads a multi-line batch of user input.
+ bool
+ GetLines (int first_line_number, StringList &lines, bool &interrupted);
+
+ private:
+
+ /// Sets the lowest line number for multi-line editing sessions. A value of zero suppresses
+ /// line number printing in the prompt.
+ void
+ SetBaseLineNumber (int line_number);
+
+ /// Returns the complete prompt by combining the prompt or continuation prompt with line numbers
+ /// as appropriate. The line index is a zero-based index into the current multi-line session.
+ std::string
+ PromptForIndex (int line_index);
+
+ /// Sets the current line index between line edits to allow free movement between lines. Updates
+ /// the prompt to match.
+ void
+ SetCurrentLine (int line_index);
+
+ /// Determines the width of the prompt in characters. The width is guaranteed to be the same for
+ /// all lines of the current multi-line session.
+ int
+ GetPromptWidth();
+
+ /// Returns true if the underlying EditLine session's keybindings are Emacs-based, or false if
+ /// they are VI-based.
+ bool
+ IsEmacs();
+
+ /// Returns true if the current EditLine buffer contains nothing but spaces, or is empty.
+ bool
+ IsOnlySpaces();
+
+ /// Helper method used by MoveCursor to determine relative line position.
+ int
+ GetLineIndexForLocation (CursorLocation location, int cursor_row);
+
+ /// Move the cursor from one well-established location to another using relative line positioning
+ /// and absolute column positioning.
+ void
+ MoveCursor (CursorLocation from, CursorLocation to);
+
+ /// Clear from cursor position to bottom of screen and print input lines including prompts, optionally
+ /// starting from a specific line. Lines are drawn with an extra space at the end to reserve room for
+ /// the rightmost cursor position.
+ void
+ DisplayInput (int firstIndex = 0);
+
+ /// Counts the number of rows a given line of content will end up occupying, taking into account both
+ /// the preceding prompt and a single trailing space occupied by a cursor when at the end of the line.
+ int
+ CountRowsForLine (const EditLineStringType & content);
+
+ /// Save the line currently being edited
+ void
+ SaveEditedLine();
+
+ /// Convert the current input lines into a UTF8 StringList
+ StringList
+ GetInputAsStringList(int line_count = UINT32_MAX);
+
+ /// Replaces the current multi-line session with the next entry from history. When the parameter is
+ /// true it will take the next earlier entry from history, when it is false it takes the next most
+ /// recent.
+ unsigned char
+ RecallHistory (bool earlier);
+
+ /// Character reading implementation for EditLine that supports our multi-line editing trickery.
+ int
+ GetCharacter (EditLineCharType * c);
+
+ /// Prompt implementation for EditLine.
+ const char *
+ Prompt();
+
+ /// Line break command used when return is pressed in multi-line mode.
+ unsigned char
+ BreakLineCommand (int ch);
+
+ /// Delete command used when delete is pressed in multi-line mode.
+ unsigned char
+ DeleteNextCharCommand (int ch);
+
+ /// Delete command used when backspace is pressed in multi-line mode.
+ unsigned char
+ DeletePreviousCharCommand (int ch);
+
+ /// Line navigation command used when ^P or up arrow are pressed in multi-line mode.
+ unsigned char
+ PreviousLineCommand (int ch);
+
+ /// Line navigation command used when ^N or down arrow are pressed in multi-line mode.
+ unsigned char
+ NextLineCommand (int ch);
+
+ /// Buffer start command used when Esc < is typed in multi-line emacs mode.
+ unsigned char
+ BufferStartCommand (int ch);
+
+ /// Buffer end command used when Esc > is typed in multi-line emacs mode.
+ unsigned char
+ BufferEndCommand (int ch);
+
+ /// Context-sensitive tab insertion or code completion command used when the tab key is typed.
+ unsigned char
+ TabCommand (int ch);
+
+ /// Respond to normal character insertion by fixing line indentation
+ unsigned char
+ FixIndentationCommand (int ch);
+
+ /// Revert line command used when moving between lines.
+ unsigned char
+ RevertLineCommand (int ch);
+
+ /// Ensures that the current EditLine instance is properly configured for single or multi-line editing.
+ void
+ ConfigureEditor (bool multiline);
+
+ private:
+#if LLDB_EDITLINE_USE_WCHAR
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> m_utf8conv;
+#endif
+ ::EditLine * m_editline = nullptr;
+ EditlineHistorySP m_history_sp;
+ bool m_in_history = false;
+ std::vector<EditLineStringType> m_live_history_lines;
+ bool m_multiline_enabled = false;
+ std::vector<EditLineStringType> m_input_lines;
+ EditorStatus m_editor_status;
+ bool m_editor_getting_char = false;
+ bool m_color_prompts = true;
+ int m_terminal_width = 0;
+ int m_base_line_number = 0;
+ unsigned m_current_line_index = 0;
+ int m_current_line_rows = -1;
+ int m_revert_cursor_index = 0;
+ int m_line_number_digits = 3;
+ std::string m_set_prompt;
+ std::string m_set_continuation_prompt;
+ std::string m_current_prompt;
+ bool m_needs_prompt_repaint = false;
+ std::string m_editor_name;
+ FILE * m_input_file;
+ FILE * m_output_file;
+ FILE * m_error_file;
+ ConnectionFileDescriptor m_input_connection;
+ IsInputCompleteCallbackType m_is_input_complete_callback = nullptr;
+ void * m_is_input_complete_callback_baton = nullptr;
+ FixIndentationCallbackType m_fix_indentation_callback = nullptr;
+ void * m_fix_indentation_callback_baton = nullptr;
+ const char * m_fix_indentation_callback_chars = nullptr;
+ CompleteCallbackType m_completion_callback = nullptr;
+ void * m_completion_callback_baton = nullptr;
};
- ::EditLine *m_editline;
- EditlineHistorySP m_history_sp;
- std::string m_prompt;
- std::string m_lines_prompt;
- lldb_private::Predicate<bool> m_getting_char;
- CompleteCallbackType m_completion_callback;
- void *m_completion_callback_baton;
- LineCompletedCallbackType m_line_complete_callback;
- void *m_line_complete_callback_baton;
- Command m_lines_command;
- uint32_t m_line_offset;
- uint32_t m_lines_curr_line;
- uint32_t m_lines_max_line;
- ConnectionFileDescriptor m_file;
- bool m_prompt_with_line_numbers;
- bool m_getting_line;
- bool m_got_eof; // Set to true when we detect EOF
- bool m_interrupted;
-
- DISALLOW_COPY_AND_ASSIGN(Editline);
-};
-
-} // namespace lldb_private
+}
#endif // #if defined(__cplusplus)
-#endif // liblldb_Host_h_
+#endif // liblldb_Editline_h_
Modified: lldb/trunk/include/lldb/Interpreter/CommandInterpreter.h
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Interpreter/CommandInterpreter.h?rev=222163&r1=222162&r2=222163&view=diff
==============================================================================
--- lldb/trunk/include/lldb/Interpreter/CommandInterpreter.h (original)
+++ lldb/trunk/include/lldb/Interpreter/CommandInterpreter.h Mon Nov 17 13:06:59 2014
@@ -627,6 +627,9 @@ public:
return m_quit_requested;
}
+ lldb::IOHandlerSP
+ GetIOHandler(bool force_create = false, CommandInterpreterRunOptions *options = NULL);
+
bool
GetStoppedForCrash () const
{
Modified: lldb/trunk/source/Commands/CommandObjectCommands.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Commands/CommandObjectCommands.cpp?rev=222163&r1=222162&r2=222163&view=diff
==============================================================================
--- lldb/trunk/source/Commands/CommandObjectCommands.cpp (original)
+++ lldb/trunk/source/Commands/CommandObjectCommands.cpp Mon Nov 17 13:06:59 2014
@@ -1030,11 +1030,15 @@ protected:
if (argc == 1)
{
Debugger &debugger = m_interpreter.GetDebugger();
+ bool color_prompt = debugger.GetUseColor();
const bool multiple_lines = true; // Get multiple lines
IOHandlerSP io_handler_sp (new IOHandlerEditline (debugger,
+ IOHandler::Type::Other,
"lldb-regex", // Name of input reader for history
"\033[K> ", // Prompt and clear line
+ NULL, // Continuation prompt
multiple_lines,
+ color_prompt,
0, // Don't show line numbers
*this));
Modified: lldb/trunk/source/Commands/CommandObjectExpression.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Commands/CommandObjectExpression.cpp?rev=222163&r1=222162&r2=222163&view=diff
==============================================================================
--- lldb/trunk/source/Commands/CommandObjectExpression.cpp (original)
+++ lldb/trunk/source/Commands/CommandObjectExpression.cpp Mon Nov 17 13:06:59 2014
@@ -425,11 +425,15 @@ CommandObjectExpression::GetMultilineExp
m_expr_line_count = 0;
Debugger &debugger = GetCommandInterpreter().GetDebugger();
+ bool color_prompt = debugger.GetUseColor();
const bool multiple_lines = true; // Get multiple lines
IOHandlerSP io_handler_sp (new IOHandlerEditline (debugger,
+ IOHandler::Type::Expression,
"lldb-expr", // Name of input reader for history
NULL, // No prompt
+ NULL, // Continuation prompt
multiple_lines,
+ color_prompt,
1, // Show line numbers starting at 1
*this));
Modified: lldb/trunk/source/Core/IOHandler.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Core/IOHandler.cpp?rev=222163&r1=222162&r2=222163&view=diff
==============================================================================
--- lldb/trunk/source/Core/IOHandler.cpp (original)
+++ lldb/trunk/source/Core/IOHandler.cpp Mon Nov 17 13:06:59 2014
@@ -38,8 +38,9 @@
using namespace lldb;
using namespace lldb_private;
-IOHandler::IOHandler (Debugger &debugger) :
+IOHandler::IOHandler (Debugger &debugger, IOHandler::Type type) :
IOHandler (debugger,
+ type,
StreamFileSP(), // Adopt STDIN from top input reader
StreamFileSP(), // Adopt STDOUT from top input reader
StreamFileSP(), // Adopt STDERR from top input reader
@@ -49,6 +50,7 @@ IOHandler::IOHandler (Debugger &debugger
IOHandler::IOHandler (Debugger &debugger,
+ IOHandler::Type type,
const lldb::StreamFileSP &input_sp,
const lldb::StreamFileSP &output_sp,
const lldb::StreamFileSP &error_sp,
@@ -57,7 +59,9 @@ IOHandler::IOHandler (Debugger &debugger
m_input_sp (input_sp),
m_output_sp (output_sp),
m_error_sp (error_sp),
+ m_popped (false),
m_flags (flags),
+ m_type (type),
m_user_data (NULL),
m_done (false),
m_active (false)
@@ -153,13 +157,28 @@ IOHandler::GetIsRealTerminal ()
return GetInputStreamFile()->GetFile().GetIsRealTerminal();
}
+void
+IOHandler::SetPopped (bool b)
+{
+ m_popped.SetValue(b, eBroadcastOnChange);
+}
+
+void
+IOHandler::WaitForPop ()
+{
+ m_popped.WaitForValueEqualTo(true);
+}
+
IOHandlerConfirm::IOHandlerConfirm (Debugger &debugger,
const char *prompt,
bool default_response) :
IOHandlerEditline(debugger,
+ IOHandler::Type::Confirm,
NULL, // NULL editline_name means no history loaded/saved
- NULL,
+ NULL, // No prompt
+ NULL, // No continuation prompt
false, // Multi-line
+ false, // Don't colorize the prompt (i.e. the confirm message.)
0,
*this),
m_default_response (default_response),
@@ -312,42 +331,56 @@ IOHandlerDelegate::IOHandlerComplete (IO
IOHandlerEditline::IOHandlerEditline (Debugger &debugger,
+ IOHandler::Type type,
const char *editline_name, // Used for saving history files
const char *prompt,
+ const char *continuation_prompt,
bool multi_line,
+ bool color_prompts,
uint32_t line_number_start,
IOHandlerDelegate &delegate) :
IOHandlerEditline(debugger,
+ type,
StreamFileSP(), // Inherit input from top input reader
StreamFileSP(), // Inherit output from top input reader
StreamFileSP(), // Inherit error from top input reader
0, // Flags
editline_name, // Used for saving history files
prompt,
+ continuation_prompt,
multi_line,
+ color_prompts,
line_number_start,
delegate)
{
}
IOHandlerEditline::IOHandlerEditline (Debugger &debugger,
+ IOHandler::Type type,
const lldb::StreamFileSP &input_sp,
const lldb::StreamFileSP &output_sp,
const lldb::StreamFileSP &error_sp,
uint32_t flags,
const char *editline_name, // Used for saving history files
const char *prompt,
+ const char *continuation_prompt,
bool multi_line,
+ bool color_prompts,
uint32_t line_number_start,
IOHandlerDelegate &delegate) :
- IOHandler (debugger, input_sp, output_sp, error_sp, flags),
+ IOHandler (debugger, type, input_sp, output_sp, error_sp, flags),
#ifndef LLDB_DISABLE_LIBEDIT
m_editline_ap (),
#endif
m_delegate (delegate),
m_prompt (),
+ m_continuation_prompt(),
+ m_current_lines_ptr (NULL),
m_base_line_number (line_number_start),
- m_multi_line (multi_line)
+ m_curr_line_idx (UINT32_MAX),
+ m_multi_line (multi_line),
+ m_color_prompts (color_prompts),
+ m_interrupt_exits (true)
{
SetPrompt(prompt);
@@ -364,17 +397,25 @@ IOHandlerEditline::IOHandlerEditline (De
if (use_editline)
{
m_editline_ap.reset(new Editline (editline_name,
- prompt ? prompt : "",
- multi_line,
GetInputFILE (),
GetOutputFILE (),
- GetErrorFILE ()));
- if (m_base_line_number > 0)
- m_editline_ap->ShowLineNumbers(true, m_base_line_number);
- m_editline_ap->SetLineCompleteCallback (LineCompletedCallback, this);
+ GetErrorFILE (),
+ m_color_prompts));
+ m_editline_ap->SetIsInputCompleteCallback (IsInputCompleteCallback, this);
m_editline_ap->SetAutoCompleteCallback (AutoCompleteCallback, this);
+ // See if the delegate supports fixing indentation
+ const char *indent_chars = delegate.IOHandlerGetFixIndentationCharacters();
+ if (indent_chars)
+ {
+ // The delegate does support indentation, hook it up so when any indentation
+ // character is typed, the delegate gets a chance to fix it
+ m_editline_ap->SetFixIndentationCallback (FixIndentationCallback, this, indent_chars);
+ }
}
#endif
+ SetBaseLineNumber (m_base_line_number);
+ SetPrompt(prompt ? prompt : "");
+ SetContinuationPrompt(continuation_prompt);
}
IOHandlerEditline::~IOHandlerEditline ()
@@ -384,6 +425,20 @@ IOHandlerEditline::~IOHandlerEditline ()
#endif
}
+void
+IOHandlerEditline::Activate ()
+{
+ IOHandler::Activate();
+ m_delegate.IOHandlerActivated(*this);
+}
+
+void
+IOHandlerEditline::Deactivate ()
+{
+ IOHandler::Deactivate();
+ m_delegate.IOHandlerDeactivated(*this);
+}
+
bool
IOHandlerEditline::GetLine (std::string &line, bool &interrupted)
@@ -391,7 +446,7 @@ IOHandlerEditline::GetLine (std::string
#ifndef LLDB_DISABLE_LIBEDIT
if (m_editline_ap)
{
- return m_editline_ap->GetLine(line, interrupted).Success();
+ return m_editline_ap->GetLine (line, interrupted);
}
else
{
@@ -403,7 +458,14 @@ IOHandlerEditline::GetLine (std::string
{
if (GetIsInteractive())
{
- const char *prompt = GetPrompt();
+ const char *prompt = NULL;
+
+ if (m_multi_line && m_curr_line_idx > 0)
+ prompt = GetContinuationPrompt();
+
+ if (prompt == NULL)
+ prompt = GetPrompt();
+
if (prompt && prompt[0])
{
FILE *out = GetOutputFILE();
@@ -468,15 +530,23 @@ IOHandlerEditline::GetLine (std::string
#ifndef LLDB_DISABLE_LIBEDIT
-LineStatus
-IOHandlerEditline::LineCompletedCallback (Editline *editline,
+bool
+IOHandlerEditline::IsInputCompleteCallback (Editline *editline,
StringList &lines,
- uint32_t line_idx,
- Error &error,
void *baton)
{
IOHandlerEditline *editline_reader = (IOHandlerEditline *) baton;
- return editline_reader->m_delegate.IOHandlerLinesUpdated(*editline_reader, lines, line_idx, error);
+ return editline_reader->m_delegate.IOHandlerIsInputComplete(*editline_reader, lines);
+}
+
+int
+IOHandlerEditline::FixIndentationCallback (Editline *editline,
+ const StringList &lines,
+ int cursor_position,
+ void *baton)
+{
+ IOHandlerEditline *editline_reader = (IOHandlerEditline *) baton;
+ return editline_reader->m_delegate.IOHandlerFixIndentation(*editline_reader, lines, cursor_position);
}
int
@@ -534,33 +604,62 @@ IOHandlerEditline::SetPrompt (const char
return true;
}
+const char *
+IOHandlerEditline::GetContinuationPrompt ()
+{
+ if (m_continuation_prompt.empty())
+ return NULL;
+ return m_continuation_prompt.c_str();
+}
+
+
+void
+IOHandlerEditline::SetContinuationPrompt (const char *p)
+{
+ if (p && p[0])
+ m_continuation_prompt = p;
+ else
+ m_continuation_prompt.clear();
+
+ if (m_editline_ap)
+ m_editline_ap->SetContinuationPrompt (m_continuation_prompt.empty() ? NULL : m_continuation_prompt.c_str());
+}
+
+
void
IOHandlerEditline::SetBaseLineNumber (uint32_t line)
{
m_base_line_number = line;
-#ifndef LLDB_DISABLE_LIBEDIT
+}
+
+uint32_t
+IOHandlerEditline::GetCurrentLineIndex () const
+{
+#ifdef LLDB_DISABLE_LIBEDIT
if (m_editline_ap)
- m_editline_ap->ShowLineNumbers (true, line);
+ return m_editline_ap->GetCurrentLine();
#endif
-
+ return m_curr_line_idx;
}
+
bool
IOHandlerEditline::GetLines (StringList &lines, bool &interrupted)
{
+ m_current_lines_ptr = &lines;
+
bool success = false;
#ifndef LLDB_DISABLE_LIBEDIT
if (m_editline_ap)
{
- std::string end_token;
- success = m_editline_ap->GetLines(end_token, lines, interrupted).Success();
+ return m_editline_ap->GetLines (m_base_line_number, lines, interrupted);
}
else
{
#endif
- LineStatus lines_status = LineStatus::Success;
+ bool done = false;
Error error;
- while (lines_status == LineStatus::Success)
+ while (!done)
{
// Show line numbers if we are asked to
std::string line;
@@ -571,29 +670,19 @@ IOHandlerEditline::GetLines (StringList
::fprintf(out, "%u%s", m_base_line_number + (uint32_t)lines.GetSize(), GetPrompt() == NULL ? " " : "");
}
+ m_curr_line_idx = lines.GetSize();
+
bool interrupted = false;
- if (GetLine(line, interrupted))
+ if (GetLine(line, interrupted) && !interrupted)
{
- if (interrupted)
- {
- lines_status = LineStatus::Done;
- }
- else
- {
- lines.AppendString(line);
- lines_status = m_delegate.IOHandlerLinesUpdated(*this, lines, lines.GetSize() - 1, error);
- }
+ lines.AppendString(line);
+ done = m_delegate.IOHandlerIsInputComplete(*this, lines);
}
else
{
- lines_status = LineStatus::Done;
+ done = true;
}
}
-
- // Call the IOHandlerLinesUpdated function with UINT32_MAX as the line
- // number to indicate all lines are complete
- m_delegate.IOHandlerLinesUpdated(*this, lines, UINT32_MAX, error);
-
success = lines.GetSize() > 0;
#ifndef LLDB_DISABLE_LIBEDIT
}
@@ -618,12 +707,14 @@ IOHandlerEditline::Run ()
{
if (interrupted)
{
- m_done = true;
+ m_done = m_interrupt_exits;
+ m_delegate.IOHandlerInputInterrupted (*this, line);
+
}
else
{
line = lines.CopyList();
- m_delegate.IOHandlerInputComplete(*this, line);
+ m_delegate.IOHandlerInputComplete (*this, line);
}
}
else
@@ -635,8 +726,10 @@ IOHandlerEditline::Run ()
{
if (GetLine(line, interrupted))
{
- if (!interrupted)
- m_delegate.IOHandlerInputComplete(*this, line);
+ if (interrupted)
+ m_delegate.IOHandlerInputInterrupted (*this, line);
+ else
+ m_delegate.IOHandlerInputComplete (*this, line);
}
else
{
@@ -5375,7 +5468,7 @@ protected:
DisplayOptions ValueObjectListDelegate::g_options = { true };
IOHandlerCursesGUI::IOHandlerCursesGUI (Debugger &debugger) :
- IOHandler (debugger)
+ IOHandler (debugger, IOHandler::Type::Curses)
{
}
Modified: lldb/trunk/source/Host/common/Editline.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Host/common/Editline.cpp?rev=222163&r1=222162&r2=222163&view=diff
==============================================================================
--- lldb/trunk/source/Host/common/Editline.cpp (original)
+++ lldb/trunk/source/Host/common/Editline.cpp Mon Nov 17 13:06:59 2014
@@ -7,527 +7,881 @@
//
//===----------------------------------------------------------------------===//
+#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/StreamString.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 <limits.h>
-
-using namespace lldb;
using namespace lldb_private;
+using namespace lldb_private::line_editor;
-namespace lldb_private {
- typedef std::weak_ptr<EditlineHistory> EditlineHistoryWP;
-
-
- // EditlineHistory objects are sometimes shared between multiple
- // Editline instances with the same program name. This class allows
- // multiple editline instances to
- //
-
- 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_init();
- ::history (m_history, &m_event, H_SETSIZE, size);
- if (unique_entries)
- ::history (m_history, &m_event, H_SETUNIQUE, 1);
- }
-
- const char *
- GetHistoryFilePath()
+// 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)
{
- if (m_path.empty() && m_history && !m_prefix.empty())
- {
- char history_path[PATH_MAX];
- ::snprintf (history_path, sizeof(history_path), "~/.%s-history", m_prefix.c_str());
- m_path = std::move(FileSpec(history_path, true).GetPath());
- }
- if (m_path.empty())
- return NULL;
- return m_path.c_str();
+ 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)
+{
+ 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.
- public:
-
- ~EditlineHistory()
+ class EditlineHistory
{
- Save ();
-
- if (m_history)
+ 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 ()
{
- ::history_end (m_history);
- m_history = NULL;
+ 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);
}
- }
- 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())
+ 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()
{
- history_sp = pos->second.lock();
- if (history_sp)
- return history_sp;
- g_weak_map.erase(pos);
+ Save();
+
+ if (m_history)
+ {
+ history_wend (m_history);
+ m_history = NULL;
+ }
}
- history_sp.reset(new EditlineHistory(prefix, 800, true));
- g_weak_map[prefix] = history_sp;
- return history_sp;
- }
-
- bool IsValid() const
- {
- return m_history != NULL;
- }
-
- ::History *
- GetHistoryPtr ()
- {
- return m_history;
- }
-
- void
- Enter (const char *line_cstr)
- {
- if (m_history)
- ::history (m_history, &m_event, H_ENTER, line_cstr);
- }
-
- bool
- Load ()
- {
- if (m_history)
+
+ static EditlineHistorySP
+ GetHistory (const std::string &prefix)
{
- const char *path = GetHistoryFilePath();
- if (path)
+ 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 (m_history, &m_event, H_LOAD, path);
- return true;
+ 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;
}
- return false;
- }
-
- bool
- Save ()
- {
- if (m_history)
+
+ bool IsValid() const
+ {
+ return m_history != NULL;
+ }
+
+ HistoryW *
+ GetHistoryPtr ()
+ {
+ return m_history;
+ }
+
+ void
+ Enter (const EditLineCharType *line_cstr)
{
- const char *path = GetHistoryFilePath();
- if (path)
+ if (m_history)
+ history_w (m_history, &m_event, H_ENTER, line_cstr);
+ }
+
+ bool
+ Load ()
+ {
+ if (m_history)
{
- ::history (m_history, &m_event, H_SAVE, path);
- return true;
+ const char *path = GetHistoryFilePath();
+ if (path)
+ {
+ history_w (m_history, &m_event, H_LOAD, path);
+ return true;
+ }
}
+ return false;
}
- return false;
- }
-
- protected:
- ::History *m_history; // The history object
- ::HistEvent 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
- };
+
+ 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
+//------------------------------------------------------------------
-static const char k_prompt_escape_char = '\1';
+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);
+}
-Editline::Editline (const char *prog, // prog can't be NULL
- const char *prompt, // can be NULL for no prompt
- bool configure_for_multiline,
- FILE *fin,
- FILE *fout,
- FILE *ferr) :
- m_editline (NULL),
- m_history_sp (),
- m_prompt (),
- m_lines_prompt (),
- m_getting_char (false),
- 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_line_offset (0),
- m_lines_curr_line (0),
- m_lines_max_line (0),
- m_file (fileno(fin), false),
- m_prompt_with_line_numbers (false),
- m_getting_line (false),
- m_got_eof (false),
- m_interrupted (false)
+std::string
+Editline::PromptForIndex (int line_index)
{
- if (prog && prog[0])
+ 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)
{
- m_editline = ::el_init(prog, fin, fout, ferr);
-
- // Get a shared history instance
- m_history_sp = EditlineHistory::GetHistory(prog);
+ prompt = ": ";
}
- else
+ std::string continuation_prompt = prompt;
+ if (m_set_continuation_prompt.length() > 0)
{
- m_editline = ::el_init("lldb-tmp", fin, fout, ferr);
+ 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 (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);
-
- // only defined for newer versions of editline
-#ifdef EL_PROMPT_ESC
- ::el_set (m_editline, EL_PROMPT_ESC, GetPromptCallback, k_prompt_escape_char);
-#else
- // fall back on old prompt setting code
- ::el_set (m_editline, EL_PROMPT, GetPromptCallback);
-#endif
- ::el_set (m_editline, EL_EDITOR, "emacs");
- if (m_history_sp && m_history_sp->IsValid())
+ if (use_line_numbers)
{
- ::el_set (m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr());
- }
- ::el_set (m_editline, EL_ADDFN, "lldb-complete", "Editline completion function", Editline::CallbackComplete);
- // Keep old "lldb_complete" mapping for older clients that used this in their .editrc. editline also
- // has a bad bug where if you have a bind command that tries to bind to a function name that doesn't
- // exist, it will corrupt the heap and probably crash your process later.
- ::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 auto complete
-
- if (configure_for_multiline)
- {
- // Use escape sequences for control characters due to bugs in editline
- // where "-k up" and "-k down" don't always work.
- ::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
- // Bindings for next/prev history
- ::el_set (m_editline, EL_BIND, "^P", "ed-prev-history", NULL); // Map up arrow
- ::el_set (m_editline, EL_BIND, "^N", "ed-next-history", NULL); // Map down arrow
+ 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());
}
- else
- {
- // Use escape sequences for control characters due to bugs in editline
- // where "-k up" and "-k down" don't always work.
- ::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
- }
-
- // Source $PWD/.editrc then $HOME/.editrc
- ::el_source (m_editline, NULL);
-
- // 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();
+ return (line_index == 0) ? prompt : continuation_prompt;
}
-Editline::~Editline()
+void
+Editline::SetCurrentLine (int line_index)
{
- // 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();
-
- // 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;
+ m_current_line_index = line_index;
+ m_current_prompt = PromptForIndex (line_index);
}
-void
-Editline::SetGetCharCallback (GetCharCallbackType callback)
+int
+Editline::GetPromptWidth()
{
- ::el_set (m_editline, EL_GETCFN, callback);
+ return (int)PromptForIndex (0).length();
}
bool
-Editline::LoadHistory ()
+Editline::IsEmacs()
{
- if (m_history_sp)
- return m_history_sp->Load();
- return false;
+ const char * editor;
+ el_get (m_editline, EL_EDITOR, &editor);
+ return editor[0] == 'e';
}
bool
-Editline::SaveHistory ()
+Editline::IsOnlySpaces()
{
- if (m_history_sp)
- return m_history_sp->Save();
- return false;
+ const LineInfoW * info = el_wline (m_editline);
+ for (const EditLineCharType * character = info->buffer; character < info->lastchar; character++)
+ {
+ if (*character != ' ') return false;
+ }
+ return true;
}
-
-Error
-Editline::PrivateGetLine(std::string &line)
+int
+Editline::GetLineIndexForLocation (CursorLocation location, int cursor_row)
{
- Error error;
- if (m_interrupted)
- {
- error.SetErrorString("interrupted");
- return error;
- }
-
- line.clear();
- if (m_editline != NULL)
+ int line = 0;
+ if (location == CursorLocation::EditingPrompt || location == CursorLocation::BlockEnd ||
+ location == CursorLocation::EditingCursor)
{
- int line_len = 0;
- // Call el_gets to prompt the user and read the user's input.
- const char *line_cstr = ::el_gets (m_editline, &line_len);
-
- static int save_errno = (line_len < 0) ? errno : 0;
-
- if (save_errno != 0)
+ for (unsigned index = 0; index < m_current_line_index; index++)
{
- error.SetError(save_errno, eErrorTypePOSIX);
+ line += CountRowsForLine (m_input_lines[index]);
}
- else if (line_cstr)
+ if (location == CursorLocation::EditingCursor)
{
- // Decrement the length so we don't have newline characters in "line" for when
- // we assign the cstr into the std::string
- llvm::StringRef line_ref (line_cstr);
- line_ref = line_ref.rtrim("\n\r");
-
- if (!line_ref.empty() && !m_interrupted)
+ line += cursor_row;
+ }
+ else if (location == CursorLocation::BlockEnd)
+ {
+ for (unsigned index = m_current_line_index; index < m_input_lines.size(); index++)
{
- // 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_sp)
- m_history_sp->Enter(line_cstr);
-
- // Copy the part of the c string that we want (removing the newline chars)
- line = std::move(line_ref.str());
+ line += CountRowsForLine (m_input_lines[index]);
}
+ --line;
}
}
- else
+ 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)
{
- error.SetErrorString("the EditLine instance has been deleted");
+ toColumn = ((m_input_lines[m_input_lines.size() - 1].length() + GetPromptWidth()) % 80) + 1;
}
- return error;
+ fprintf (m_output_file, ANSI_SET_COLUMN_N, toColumn);
}
-
-Error
-Editline::GetLine(std::string &line, bool &interrupted)
+void
+Editline::DisplayInput (int firstIndex)
{
- Error error;
- interrupted = false;
- line.clear();
-
- // Set arrow key bindings for up and down arrows for single line
- // mode where up and down arrows do prev/next history
- m_interrupted = false;
-
- if (!m_got_eof)
+ 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++)
{
- 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;
+ 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");
}
+}
- interrupted = m_interrupted;
- 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");
- }
+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;
+}
- return error;
+void
+Editline::SaveEditedLine()
+{
+ const LineInfoW * info = el_wline (m_editline);
+ m_input_lines[m_current_line_index] = EditLineStringType (info->buffer, info->lastchar - info->buffer);
}
-size_t
-Editline::Push (const char *bytes, size_t len)
+StringList
+Editline::GetInputAsStringList(int line_count)
{
- if (m_editline)
+ StringList lines;
+ for (EditLineStringType line : m_input_lines)
{
- // Must NULL terminate the string for el_push() so we stick it
- // into a std::string first
- ::el_push(m_editline,
- const_cast<char*>(std::string (bytes, len).c_str()));
- return len;
+ 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 0;
+ return lines;
}
-
-Error
-Editline::GetLines(const std::string &end_line, StringList &lines, bool &interrupted)
+unsigned char
+Editline::RecallHistory (bool earlier)
{
- Error error;
- interrupted = false;
- if (m_getting_line)
+ 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)
{
- error.SetErrorString("already getting a line");
- return error;
+ 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;
}
- if (m_lines_curr_line > 0)
+ else
{
- error.SetErrorString("already getting lines");
- return error;
+ 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;
+ }
}
- // Set arrow key bindings for up and down arrows for multiple line
- // mode where up and down arrows do edit prev/next line
- m_interrupted = false;
+ // If we're pulling the lines from history, split them apart
+ if (m_in_history) new_input_lines = SplitLines (history_event.str);
- LineStatus line_status = LineStatus::Success;
-
- lines.Clear();
+ // 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;
+}
- FILE *out_file = GetOutputFile();
- FILE *err_file = GetErrorFile();
- m_lines_curr_line = 1;
- while (line_status != LineStatus::Done)
+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)
{
- 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;
+ // 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);
}
- else if (m_interrupted)
- {
- interrupted = true;
- line_status = LineStatus::Done;
+ 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 (m_lines_command)
+ switch (status)
{
- 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,
- const_cast<char*>(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());
- }
+ case lldb::eConnectionStatusInterrupted:
+ m_editor_status = EditorStatus::Interrupted;
+ printf ("^C\n");
+ return 0;
+
+ case lldb::eConnectionStatusSuccess: // Success
break;
- case Command::EditPrevLine:
- if (m_lines_curr_line > 1)
+
+ 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++)
{
- //::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,
- const_cast<char*>(lines[line_idx-1].c_str()));
- --m_lines_curr_line;
+#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
}
- 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,
- const_cast<char*>(lines[line_idx+1].c_str()));
- break;
+ }
+ }
+ 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;
}
- m_lines_curr_line = 0;
- m_lines_command = Command::None;
+
+ // 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;
+}
- // If we have a callback, call it one more time to let the
- // user know the lines are complete
- if (m_line_complete_callback && !interrupted)
- m_line_complete_callback (this,
- lines,
- UINT32_MAX,
- error,
- m_line_complete_callback_baton);
+unsigned char
+Editline::PreviousLineCommand (int ch)
+{
+ SaveEditedLine();
- return error;
+ 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::HandleCompletion (int ch)
+Editline::NextLineCommand (int ch)
{
- if (m_completion_callback == NULL)
- return CC_ERROR;
+ SaveEditedLine();
- const LineInfo *line_info = ::el_line(m_editline);
+ // 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,
@@ -536,25 +890,24 @@ Editline::HandleCompletion (int ch)
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 == 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));
+ 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')
+ const char *completion_str = completions.GetStringAtIndex (0);
+ if (completion_str != nullptr && *completion_str != '\0')
{
el_insertstr (m_editline, completion_str);
return CC_REDISPLAY;
@@ -563,15 +916,15 @@ Editline::HandleCompletion (int ch)
if (num_completions > 1)
{
int num_elements = num_completions + 1;
- ::fprintf (out_file, "\nAvailable completions:");
+ 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 (out_file, "\n\t%s", completion_str);
+ completion_str = completions.GetStringAtIndex (i);
+ fprintf (m_output_file, "\n\t%s", completion_str);
}
- ::fprintf (out_file, "\n");
+ fprintf (m_output_file, "\n");
}
else
{
@@ -585,17 +938,17 @@ Editline::HandleCompletion (int ch)
endpoint = num_elements;
for (; cur_pos < endpoint; cur_pos++)
{
- completion_str = completions.GetStringAtIndex(cur_pos);
- ::fprintf (out_file, "\n\t%s", completion_str);
+ completion_str = completions.GetStringAtIndex (cur_pos);
+ fprintf (m_output_file, "\n\t%s", completion_str);
}
if (cur_pos >= num_elements)
{
- ::fprintf (out_file, "\n");
+ fprintf (m_output_file, "\n");
break;
}
- ::fprintf (out_file, "\nMore (Y/n/a): ");
+ fprintf (m_output_file, "\nMore (Y/n/a): ");
reply = 'n';
got_char = el_getc(m_editline, &reply);
if (got_char == -1 || reply == 'n')
@@ -604,247 +957,381 @@ Editline::HandleCompletion (int ch)
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 (num_completions == 0)
- return CC_REFRESH_BEEP;
- else
- return CC_REDISPLAY;
+ 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::GetClientData (::EditLine *e)
+Editline::InstanceFor (EditLine * editline)
{
- Editline *editline = NULL;
- if (e && ::el_get(e, EL_CLIENTDATA, &editline) == 0)
- return editline;
- return NULL;
+ 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);
}
-FILE *
-Editline::GetInputFile ()
+Editline::~Editline()
{
- return GetFilePointer (m_editline, 0);
+ 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();
}
-FILE *
-Editline::GetOutputFile ()
+void
+Editline::SetPrompt (const char * prompt)
{
- return GetFilePointer (m_editline, 1);
+ m_set_prompt = prompt == nullptr ? "" : prompt;
}
-FILE *
-Editline::GetErrorFile ()
+void
+Editline::SetContinuationPrompt (const char * continuation_prompt)
{
- return GetFilePointer (m_editline, 2);
+ m_set_continuation_prompt = continuation_prompt == nullptr ? "" : continuation_prompt;
}
-const char *
-Editline::GetPrompt()
+void
+Editline::TerminalSizeChanged()
{
- 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();
+ 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;
+ }
}
}
-void
-Editline::SetPrompt (const char *p)
+const char *
+Editline::GetPrompt()
{
- 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;
- }
+ return m_set_prompt.c_str();
}
-FILE *
-Editline::GetFilePointer (::EditLine *e, int fd)
+uint32_t
+Editline::GetCurrentLine()
{
- FILE *file_ptr = NULL;
- if (e && ::el_get(e, EL_GETFP, fd, &file_ptr) == 0)
- return file_ptr;
- return NULL;
+ return m_current_line_index;
}
-unsigned char
-Editline::CallbackEditPrevLine (::EditLine *e, int ch)
+void
+Editline::Hide()
{
- Editline *editline = GetClientData (e);
- if (editline->m_lines_curr_line > 1)
+ // Make sure we're at a stable location waiting for input
+ while (m_editor_status == EditorStatus::Editing && !m_editor_getting_char)
{
- editline->m_lines_command = Command::EditPrevLine;
- return CC_NEWLINE;
+ usleep(100000);
}
- 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)
+
+ // Clear the existing input
+ if (m_editor_status == EditorStatus::Editing)
{
- editline->m_lines_command = Command::EditNextLine;
- return CC_NEWLINE;
+ MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart);
+ fprintf(m_output_file, ANSI_CLEAR_BELOW);
}
- return CC_ERROR;
}
-unsigned char
-Editline::CallbackComplete (::EditLine *e, int ch)
+void
+Editline::Refresh()
{
- Editline *editline = GetClientData (e);
- if (editline)
- return editline->HandleCompletion (ch);
- return CC_ERROR;
+ if (m_editor_status == EditorStatus::Editing)
+ {
+ DisplayInput();
+ MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor);
+ }
}
-const char *
-Editline::GetPromptCallback (::EditLine *e)
+bool
+Editline::Interrupt()
{
- Editline *editline = GetClientData (e);
- if (editline)
- return editline->GetPrompt();
- return "";
+ 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
}
-int
-Editline::GetCharFromInputFileCallback (EditLine *e, char *c)
+void
+Editline::SetAutoCompleteCallback (CompleteCallbackType callback, void * baton)
{
- Editline *editline = GetClientData (e);
- if (editline && editline->m_got_eof == false)
- {
- FILE *f = editline->GetInputFile();
- if (f == NULL)
- {
- editline->m_got_eof = true;
- return 0;
- }
-
-
- while (1)
- {
- lldb::ConnectionStatus status = eConnectionStatusSuccess;
- char ch = 0;
- // When we start to call el_gets() the editline library needs to
- // output the prompt
- editline->m_getting_char.SetValue(true, eBroadcastAlways);
- const size_t n = editline->m_file.Read(&ch, 1, UINT32_MAX, status, NULL);
- editline->m_getting_char.SetValue(false, eBroadcastAlways);
- if (n)
- {
- if (ch == '\x04')
- {
- // Only turn a CTRL+D into a EOF if we receive the
- // CTRL+D an empty line, otherwise it will forward
- // delete the character at the cursor
- const LineInfo *line_info = ::el_line(e);
- if (line_info != NULL &&
- line_info->buffer == line_info->cursor &&
- line_info->cursor == line_info->lastchar)
- {
- editline->m_got_eof = true;
- break;
- }
- }
-
- if (status == eConnectionStatusEndOfFile)
- {
- editline->m_got_eof = true;
- break;
- }
- else
- {
- *c = ch;
- return 1;
- }
- }
- else
- {
- switch (status)
- {
- case eConnectionStatusInterrupted:
- editline->m_interrupted = true;
- *c = '\n';
- return 1;
-
- case eConnectionStatusSuccess: // Success
- break;
-
- case eConnectionStatusError: // Check GetError() for details
- case eConnectionStatusTimedOut: // Request timed out
- case eConnectionStatusEndOfFile: // End-of-file encountered
- case eConnectionStatusNoConnection: // No connection
- case eConnectionStatusLostConnection: // Lost connection while connected to a valid connection
- editline->m_got_eof = true;
- break;
- }
- }
- }
- }
- return 0;
+ m_completion_callback = callback;
+ m_completion_callback_baton = baton;
}
void
-Editline::Hide ()
+Editline::SetIsInputCompleteCallback (IsInputCompleteCallbackType callback, void * baton)
{
- if (m_getting_line)
- {
- // If we are getting a line, we might have started to call el_gets() and
- // it might be printing the prompt. Here we make sure we are actually getting
- // a character. This way we know the entire prompt has been printed.
- TimeValue timeout = TimeValue::Now();
- timeout.OffsetWithSeconds(1);
- if (m_getting_char.WaitForValueEqualTo(true, &timeout))
- {
- 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));
- }
- }
- }
+ 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;
+}
-void
-Editline::Refresh()
+bool
+Editline::GetLine (std::string &line, bool &interrupted)
{
- if (m_getting_line)
+ 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 we are getting a line, we might have started to call el_gets() and
- // it might be printing the prompt. Here we make sure we are actually getting
- // a character. This way we know the entire prompt has been printed.
- TimeValue timeout = TimeValue::Now();
- timeout.OffsetWithSeconds(1);
- if (m_getting_char.WaitForValueEqualTo(true, &timeout))
+ if (input == nullptr)
{
- ::el_set (m_editline, EL_REFRESH);
+ 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::Interrupt ()
+Editline::GetLines (int first_line_number, StringList &lines, bool &interrupted)
{
- m_interrupted = true;
- if (m_getting_line || m_lines_curr_line > 0)
- return m_file.InterruptRead();
- return false; // Interrupt not handled as we weren't getting a line or lines
+ 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;
}
Modified: lldb/trunk/source/Interpreter/CommandInterpreter.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Interpreter/CommandInterpreter.cpp?rev=222163&r1=222162&r2=222163&view=diff
==============================================================================
--- lldb/trunk/source/Interpreter/CommandInterpreter.cpp (original)
+++ lldb/trunk/source/Interpreter/CommandInterpreter.cpp Mon Nov 17 13:06:59 2014
@@ -2737,13 +2737,16 @@ CommandInterpreter::HandleCommandsFromFi
lldb::StreamFileSP empty_stream_sp;
m_command_source_flags.push_back(flags);
IOHandlerSP io_handler_sp (new IOHandlerEditline (debugger,
+ IOHandler::Type::CommandInterpreter,
input_file_sp,
empty_stream_sp, // Pass in an empty stream so we inherit the top input reader output stream
empty_stream_sp, // Pass in an empty stream so we inherit the top input reader error stream
flags,
nullptr, // Pass in NULL for "editline_name" so no history is saved, or written
debugger.GetPrompt(),
+ NULL,
false, // Not multi-line
+ debugger.GetUseColor(),
0,
*this));
const bool old_async_execution = debugger.GetAsyncExecution();
@@ -3181,9 +3184,12 @@ CommandInterpreter::GetLLDBCommandsFromI
{
Debugger &debugger = GetDebugger();
IOHandlerSP io_handler_sp (new IOHandlerEditline (debugger,
+ IOHandler::Type::CommandList,
"lldb", // Name of input reader for history
prompt, // Prompt
+ NULL, // Continuation prompt
true, // Get multiple lines
+ debugger.GetUseColor(),
0, // Don't show line numbers
delegate)); // IOHandlerDelegate
@@ -3207,9 +3213,12 @@ CommandInterpreter::GetPythonCommandsFro
{
Debugger &debugger = GetDebugger();
IOHandlerSP io_handler_sp (new IOHandlerEditline (debugger,
+ IOHandler::Type::PythonCode,
"lldb-python", // Name of input reader for history
prompt, // Prompt
+ NULL, // Continuation prompt
true, // Get multiple lines
+ debugger.GetUseColor(),
0, // Don't show line numbers
delegate)); // IOHandlerDelegate
@@ -3230,47 +3239,65 @@ CommandInterpreter::IsActive ()
return m_debugger.IsTopIOHandler (m_command_io_handler_sp);
}
+lldb::IOHandlerSP
+CommandInterpreter::GetIOHandler(bool force_create, CommandInterpreterRunOptions *options)
+{
+ // Always re-create the IOHandlerEditline in case the input
+ // changed. The old instance might have had a non-interactive
+ // input and now it does or vice versa.
+ if (force_create || !m_command_io_handler_sp)
+ {
+ // Always re-create the IOHandlerEditline in case the input
+ // changed. The old instance might have had a non-interactive
+ // input and now it does or vice versa.
+ uint32_t flags = 0;
+
+ if (options)
+ {
+ if (options->m_stop_on_continue == eLazyBoolYes)
+ flags |= eHandleCommandFlagStopOnContinue;
+ if (options->m_stop_on_error == eLazyBoolYes)
+ flags |= eHandleCommandFlagStopOnError;
+ if (options->m_stop_on_crash == eLazyBoolYes)
+ flags |= eHandleCommandFlagStopOnCrash;
+ if (options->m_echo_commands != eLazyBoolNo)
+ flags |= eHandleCommandFlagEchoCommand;
+ if (options->m_print_results != eLazyBoolNo)
+ flags |= eHandleCommandFlagPrintResult;
+ }
+ else
+ {
+ flags = eHandleCommandFlagEchoCommand | eHandleCommandFlagPrintResult;
+ }
+
+ m_command_io_handler_sp.reset(new IOHandlerEditline (m_debugger,
+ IOHandler::Type::CommandInterpreter,
+ m_debugger.GetInputFile(),
+ m_debugger.GetOutputFile(),
+ m_debugger.GetErrorFile(),
+ flags,
+ "lldb",
+ m_debugger.GetPrompt(),
+ NULL, // Continuation prompt
+ false, // Don't enable multiple line input, just single line commands
+ m_debugger.GetUseColor(),
+ 0, // Don't show line numbers
+ *this));
+ }
+ return m_command_io_handler_sp;
+}
+
void
CommandInterpreter::RunCommandInterpreter(bool auto_handle_events,
bool spawn_thread,
CommandInterpreterRunOptions &options)
{
- // Only get one line at a time
- const bool multiple_lines = false;
- m_num_errors = 0;
- m_quit_requested = false;
+ // Always re-create the command intepreter when we run it in case
+ // any file handles have changed.
+ bool force_create = true;
+ m_debugger.PushIOHandler(GetIOHandler(force_create, &options));
m_stopped_for_crash = false;
- // Always re-create the IOHandlerEditline in case the input
- // changed. The old instance might have had a non-interactive
- // input and now it does or vice versa.
- uint32_t flags= 0;
-
- if (options.m_stop_on_continue == eLazyBoolYes)
- flags |= eHandleCommandFlagStopOnContinue;
- if (options.m_stop_on_error == eLazyBoolYes)
- flags |= eHandleCommandFlagStopOnError;
- if (options.m_stop_on_crash == eLazyBoolYes)
- flags |= eHandleCommandFlagStopOnCrash;
- if (options.m_echo_commands != eLazyBoolNo)
- flags |= eHandleCommandFlagEchoCommand;
- if (options.m_print_results != eLazyBoolNo)
- flags |= eHandleCommandFlagPrintResult;
-
-
- m_command_io_handler_sp.reset(new IOHandlerEditline (m_debugger,
- m_debugger.GetInputFile(),
- m_debugger.GetOutputFile(),
- m_debugger.GetErrorFile(),
- flags,
- "lldb",
- m_debugger.GetPrompt(),
- multiple_lines,
- 0, // Don't show line numbers
- *this));
-
- m_debugger.PushIOHandler(m_command_io_handler_sp);
-
if (auto_handle_events)
m_debugger.StartEventHandlerThread();
@@ -3281,10 +3308,10 @@ CommandInterpreter::RunCommandInterprete
else
{
m_debugger.ExecuteIOHanders();
-
+
if (auto_handle_events)
m_debugger.StopEventHandlerThread();
}
-
+
}
Modified: lldb/trunk/source/Interpreter/ScriptInterpreterPython.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Interpreter/ScriptInterpreterPython.cpp?rev=222163&r1=222162&r2=222163&view=diff
==============================================================================
--- lldb/trunk/source/Interpreter/ScriptInterpreterPython.cpp (original)
+++ lldb/trunk/source/Interpreter/ScriptInterpreterPython.cpp Mon Nov 17 13:06:59 2014
@@ -728,7 +728,7 @@ public:
IOHandlerPythonInterpreter (Debugger &debugger,
ScriptInterpreterPython *python) :
- IOHandler (debugger),
+ IOHandler (debugger, IOHandler::Type::PythonInterpreter),
m_python(python)
{
Modified: lldb/trunk/source/Target/Process.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Target/Process.cpp?rev=222163&r1=222162&r2=222163&view=diff
==============================================================================
--- lldb/trunk/source/Target/Process.cpp (original)
+++ lldb/trunk/source/Target/Process.cpp Mon Nov 17 13:06:59 2014
@@ -4893,7 +4893,7 @@ class IOHandlerProcessSTDIO :
public:
IOHandlerProcessSTDIO (Process *process,
int write_fd) :
- IOHandler(process->GetTarget().GetDebugger()),
+ IOHandler(process->GetTarget().GetDebugger(), IOHandler::Type::ProcessIO),
m_process (process),
m_read_file (),
m_write_file (write_fd, false),
More information about the lldb-commits
mailing list