From: Kate Stone Date: Mon, 17 Nov 2014 19:06:59 +0000 (+0000) Subject: Complete rewrite of interactive editing support for single- and multi-line input. X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e30f11d9ee08309d545224091bad706b9a8d4eca;p=platform%2Fupstream%2Fllvm.git 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 llvm-svn: 222163 --- diff --git a/lldb/include/lldb/Core/IOHandler.h b/lldb/include/lldb/Core/IOHandler.h index 3b1628d..8ad3820 100644 --- a/lldb/include/lldb/Core/IOHandler.h +++ b/lldb/include/lldb/Core/IOHandler.h @@ -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 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. + /// 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 LineStatus - IOHandlerLinesUpdated (IOHandler &io_handler, - StringList &lines, - uint32_t line_idx, - Error &error) + virtual bool + IOHandlerIsInputComplete (IOHandler &io_handler, + StringList &lines) { - 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. + // 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; - } - 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 true; } - 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 m_stack; + typedef std::vector collection; + collection m_stack; mutable Mutex m_mutex; IOHandler *m_top; diff --git a/lldb/include/lldb/Host/Editline.h b/lldb/include/lldb/Host/Editline.h index 5f50ab6..9fce193 100644 --- a/lldb/include/lldb/Host/Editline.h +++ b/lldb/include/lldb/Host/Editline.h @@ -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 +#include + +// components needed to handle wide characters ( , 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 +#else +#define LLDB_EDITLINE_USE_WCHAR 0 +#endif + #include "lldb/lldb-private.h" +#include "lldb/Host/ConnectionFileDescriptor.h" -#include #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 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 (); + // 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 - Refresh(); + typedef int (* EditlineGetCharCallbackType)(::EditLine * editline, EditLineCharType * c); + typedef unsigned char (* EditlineCommandCallbackType)(::EditLine * editline, int ch); + typedef const char * (* EditlinePromptCallbackType)(::EditLine * editline); - bool - Interrupt (); + class EditlineHistory; + + typedef std::shared_ptr EditlineHistorySP; - void - SetAutoCompleteCallback (CompleteCallbackType callback, - void *baton) - { - m_completion_callback = callback; - m_completion_callback_baton = baton; - } + 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, - void - SetLineCompleteCallback (LineCompletedCallbackType callback, - void *baton) - { - m_line_complete_callback = callback; - m_line_complete_callback_baton = baton; + /// 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 + }; } - - size_t - Push (const char *bytes, size_t len); - - static int - GetCharFromInputFileCallback (::EditLine *e, char *c); - - void - SetGetCharCallback (GetCharCallbackType callback); - const char * - GetPrompt(); + using namespace line_editor; - void - SetPrompt (const char *p); - - void - ShowLineNumbers (bool enable, uint32_t line_offset) + /// Instances of Editline provide an abstraction over libedit's EditLine facility. Both + /// single- and multi-line editing are supported. + class Editline { - m_prompt_with_line_numbers = enable; - m_line_offset = line_offset; - } - -private: + 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(); - Error - PrivateGetLine(std::string &line); - - unsigned char - HandleCompletion (int ch); + /// Hides the current input session in preparation for output + void + Hide(); + + /// Prepare to return to editing after a call to Hide() + void + Refresh(); - static unsigned char - CallbackEditPrevLine (::EditLine *e, int ch); - - static unsigned char - CallbackEditNextLine (::EditLine *e, int ch); - - static unsigned char - CallbackComplete (::EditLine *e, int ch); + /// 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); - static const char * - GetPromptCallback (::EditLine *e); + /// 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); - static Editline * - GetClientData (::EditLine *e); - - static FILE * - GetFilePointer (::EditLine *e, int fd); + /// 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); - enum class Command - { - None = 0, - EditPrevLine, - EditNextLine, - }; - ::EditLine *m_editline; - EditlineHistorySP m_history_sp; - std::string m_prompt; - std::string m_lines_prompt; - lldb_private::Predicate 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); -}; + /// Revert line command used when moving between lines. + unsigned char + RevertLineCommand (int ch); -} // namespace lldb_private + /// 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> m_utf8conv; +#endif + ::EditLine * m_editline = nullptr; + EditlineHistorySP m_history_sp; + bool m_in_history = false; + std::vector m_live_history_lines; + bool m_multiline_enabled = false; + std::vector 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; + }; +} #endif // #if defined(__cplusplus) -#endif // liblldb_Host_h_ +#endif // liblldb_Editline_h_ diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h index 81193fc..0c8edeb 100644 --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -627,6 +627,9 @@ public: return m_quit_requested; } + lldb::IOHandlerSP + GetIOHandler(bool force_create = false, CommandInterpreterRunOptions *options = NULL); + bool GetStoppedForCrash () const { diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp index c7341c2..22cfc65 100644 --- a/lldb/source/Commands/CommandObjectCommands.cpp +++ b/lldb/source/Commands/CommandObjectCommands.cpp @@ -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)); diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp index 079c62d..d36e3db 100644 --- a/lldb/source/Commands/CommandObjectExpression.cpp +++ b/lldb/source/Commands/CommandObjectExpression.cpp @@ -425,11 +425,15 @@ CommandObjectExpression::GetMultilineExpression () 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)); diff --git a/lldb/source/Core/IOHandler.cpp b/lldb/source/Core/IOHandler.cpp index 76e56641..bf3815b 100644 --- a/lldb/source/Core/IOHandler.cpp +++ b/lldb/source/Core/IOHandler.cpp @@ -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 (IOHandler &io_handler, 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 (Debugger &debugger, 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 &line, bool &interrupted) #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 &line, bool &interrupted) { 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 &line, bool &interrupted) #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 *p) 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 &lines, bool &interrupted) ::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) { } diff --git a/lldb/source/Host/common/Editline.cpp b/lldb/source/Host/common/Editline.cpp index 7af9f39..850ee4e 100644 --- a/lldb/source/Host/common/Editline.cpp +++ b/lldb/source/Host/common/Editline.cpp @@ -7,527 +7,881 @@ // //===----------------------------------------------------------------------===// +#include +#include +#include #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 - -using namespace lldb; using namespace lldb_private; +using namespace lldb_private::line_editor; -namespace lldb_private { - typedef std::weak_ptr 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 () +// 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 & lines) +{ + EditLineStringStreamType combined_stream; + for (EditLineStringType line : lines) + { + combined_stream << line.c_str() << "\n"; + } + return combined_stream.str(); +} + +std::vector +SplitLines (const EditLineStringType & input) +{ + std::vector result; + size_t start = 0; + while (start < input.length()) { + size_t end = input.find ('\n', start); + if (end == std::string::npos) { - m_history = ::history_init(); - ::history (m_history, &m_event, H_SETSIZE, size); - if (unique_entries) - ::history (m_history, &m_event, H_SETUNIQUE, 1); + 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 EditlineHistoryWP; + + // EditlineHistory objects are sometimes shared between multiple + // Editline instances with the same program name. - const char * - GetHistoryFilePath() + class EditlineHistory { - if (m_path.empty() && m_history && !m_prefix.empty()) + 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 () { - 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()); + 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); } - if (m_path.empty()) - return NULL; - return m_path.c_str(); - } - - public: - - ~EditlineHistory() - { - Save (); + + 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: - if (m_history) + ~EditlineHistory() { - ::history_end (m_history); - m_history = NULL; + Save(); + + if (m_history) + { + history_wend (m_history); + m_history = NULL; + } } - } - - static EditlineHistorySP - GetHistory (const std::string &prefix) - { - typedef std::map 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()) + + static EditlineHistorySP + GetHistory (const std::string &prefix) { - history_sp = pos->second.lock(); - if (history_sp) - return history_sp; - g_weak_map.erase(pos); + typedef std::map WeakHistoryMap; + static Mutex g_mutex (Mutex::eMutexTypeRecursive); + static WeakHistoryMap g_weak_map; + Mutex::Locker locker (g_mutex); + WeakHistoryMap::const_iterator pos = g_weak_map.find (prefix); + EditlineHistorySP history_sp; + if (pos != g_weak_map.end()) + { + history_sp = pos->second.lock(); + if (history_sp) + return history_sp; + g_weak_map.erase (pos); + } + history_sp.reset (new EditlineHistory (prefix, 800, true)); + g_weak_map[prefix] = history_sp; + return history_sp; } - 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) + + bool IsValid() const { - const char *path = GetHistoryFilePath(); - if (path) + return m_history != NULL; + } + + HistoryW * + GetHistoryPtr () + { + return m_history; + } + + void + Enter (const EditLineCharType *line_cstr) + { + if (m_history) + history_w (m_history, &m_event, H_ENTER, line_cstr); + } + + bool + Load () + { + if (m_history) { - ::history (m_history, &m_event, H_LOAD, path); - return true; + const char *path = GetHistoryFilePath(); + if (path) + { + history_w (m_history, &m_event, H_LOAD, path); + return true; + } } + return false; } - return false; - } - - bool - Save () - { - if (m_history) + + bool + Save () { - const char *path = GetHistoryFilePath(); - if (path) + 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_SAVE, 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 - }; + + 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'; - -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) -{ - if (prog && prog[0]) - { - m_editline = ::el_init(prog, fin, fout, ferr); - - // Get a shared history instance - m_history_sp = EditlineHistory::GetHistory(prog); +void +Editline::SetBaseLineNumber (int line_number) +{ + std::stringstream line_number_stream; + line_number_stream << line_number; + m_base_line_number = line_number; + m_line_number_digits = std::max (3, (int)line_number_stream.str().length() + 1); +} + +std::string +Editline::PromptForIndex (int line_index) +{ + bool use_line_numbers = m_multiline_enabled && m_base_line_number > 0; + std::string prompt = m_set_prompt; + if (use_line_numbers && prompt.length() == 0) + { + prompt = ": "; } - 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); + if (use_line_numbers) + { + StreamString prompt_stream; + prompt_stream.Printf("%*d%s", m_line_number_digits, m_base_line_number + line_index, + (line_index == 0) ? prompt.c_str() : continuation_prompt.c_str()); + return std::move (prompt_stream.GetString()); + } + return (line_index == 0) ? prompt : continuation_prompt; +} - //::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 +void +Editline::SetCurrentLine (int line_index) +{ + m_current_line_index = line_index; + m_current_prompt = PromptForIndex (line_index); +} - assert (m_editline); - ::el_set (m_editline, EL_CLIENTDATA, this); +int +Editline::GetPromptWidth() +{ + return (int)PromptForIndex (0).length(); +} - // 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()) +bool +Editline::IsEmacs() +{ + const char * editor; + el_get (m_editline, EL_EDITOR, &editor); + return editor[0] == 'e'; +} + +bool +Editline::IsOnlySpaces() +{ + const LineInfoW * info = el_wline (m_editline); + for (const EditLineCharType * character = info->buffer; character < info->lastchar; character++) { - ::el_set (m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr()); + if (*character != ' ') return false; } - ::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); + return true; +} - ::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) +int +Editline::GetLineIndexForLocation (CursorLocation location, int cursor_row) +{ + int line = 0; + if (location == CursorLocation::EditingPrompt || location == CursorLocation::BlockEnd || + location == CursorLocation::EditingCursor) { - // 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 + for (unsigned index = 0; index < m_current_line_index; index++) + { + line += CountRowsForLine (m_input_lines[index]); + } + if (location == CursorLocation::EditingCursor) + { + line += cursor_row; + } + else if (location == CursorLocation::BlockEnd) + { + for (unsigned index = m_current_line_index; index < m_input_lines.size(); index++) + { + line += CountRowsForLine (m_input_lines[index]); + } + --line; + } } - 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) { - // 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 + fprintf (m_output_file, (toLine > fromLine) ? ANSI_DOWN_N_ROWS : ANSI_UP_N_ROWS, std::abs (toLine - fromLine)); } - // 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(); + // Determine target column + int toColumn = 1; + if (to == CursorLocation::EditingCursor) + { + toColumn = editline_cursor_position - (editline_cursor_row * m_terminal_width) + 1; + } + else if (to == CursorLocation::BlockEnd) + { + toColumn = ((m_input_lines[m_input_lines.size() - 1].length() + GetPromptWidth()) % 80) + 1; + } + fprintf (m_output_file, ANSI_SET_COLUMN_N, toColumn); } -Editline::~Editline() +void +Editline::DisplayInput (int firstIndex) { - // 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(); + 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 : ""; - // 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); + for (int index = firstIndex; index < line_count; index++) + { + fprintf (m_output_file, "%s" "%s" "%s" EditLineStringFormatSpec " ", + faint, + PromptForIndex (index).c_str(), + unfaint, + m_input_lines[index].c_str()); + if (index < line_count - 1) fprintf (m_output_file, "\n"); + } +} - ::el_end(m_editline); - m_editline = NULL; + +int +Editline::CountRowsForLine (const EditLineStringType & content) +{ + auto prompt = PromptForIndex (0); // Prompt width is constant during an edit session + int line_length = (int)(content.length() + prompt.length()); + return (line_length / m_terminal_width) + 1; } void -Editline::SetGetCharCallback (GetCharCallbackType callback) +Editline::SaveEditedLine() { - ::el_set (m_editline, EL_GETCFN, callback); + const LineInfoW * info = el_wline (m_editline); + m_input_lines[m_current_line_index] = EditLineStringType (info->buffer, info->lastchar - info->buffer); } -bool -Editline::LoadHistory () +StringList +Editline::GetInputAsStringList(int line_count) { - if (m_history_sp) - return m_history_sp->Load(); - return false; + StringList lines; + for (EditLineStringType line : m_input_lines) + { + if (line_count == 0) break; +#if LLDB_EDITLINE_USE_WCHAR + lines.AppendString (m_utf8conv.to_bytes (line)); +#else + lines.AppendString(line); +#endif + --line_count; + } + return lines; } -bool -Editline::SaveHistory () +unsigned char +Editline::RecallHistory (bool earlier) { - if (m_history_sp) - return m_history_sp->Save(); - return false; -} + if (!m_history_sp || !m_history_sp->IsValid()) return CC_ERROR; + + HistoryW * pHistory = m_history_sp->GetHistoryPtr(); + HistEventW history_event; + std::vector new_input_lines; + + // Treat moving from the "live" entry differently + if (!m_in_history) + { + if (earlier == false) return CC_ERROR; // Can't go newer than the "live" entry + if (history_w (pHistory, &history_event, H_FIRST) == -1) return CC_ERROR; + + // Save any edits to the "live" entry in case we return by moving forward in history + // (it would be more bash-like to save over any current entry, but libedit doesn't + // offer the ability to add entries anywhere except the end.) + SaveEditedLine(); + m_live_history_lines = m_input_lines; + m_in_history = true; + } + else + { + if (history_w (pHistory, &history_event, earlier ? H_NEXT : H_PREV) == -1) + { + // Can't move earlier than the earliest entry + if (earlier) return CC_ERROR; + + // ... but moving to newer than the newest yields the "live" entry + new_input_lines = m_live_history_lines; + m_in_history = false; + } + } + + // If we're pulling the lines from history, split them apart + if (m_in_history) new_input_lines = SplitLines (history_event.str); + // Erase the current edit session and replace it with a new one + MoveCursor (CursorLocation::EditingCursor, CursorLocation::BlockStart); + m_input_lines = new_input_lines; + DisplayInput(); + + // Prepare to edit the last line when moving to previous entry, or the first line + // when moving to next entry + SetCurrentLine (m_current_line_index = earlier ? (int)m_input_lines.size() - 1 : 0); + MoveCursor (CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + return CC_NEWLINE; +} -Error -Editline::PrivateGetLine(std::string &line) +int +Editline::GetCharacter (EditLineCharType * c) { - Error error; - if (m_interrupted) + 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) { - error.SetErrorString("interrupted"); - return error; + 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; } - line.clear(); - if (m_editline != NULL) + if (m_multiline_enabled) { - 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) + // 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) { - error.SetError(save_errno, eErrorTypePOSIX); + // 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 (line_cstr) + 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) { - // 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) +#if LLDB_EDITLINE_USE_WCHAR + // After the initial interruptible read, this is guaranteed not to block + ungetc (ch, m_input_file); + *c = fgetwc (m_input_file); + if (*c != WEOF) return 1; +#else + *c = ch; + if(*c != EOF) return 1; +#endif + } + else + { + switch (status) { - // 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()); + case lldb::eConnectionStatusInterrupted: + m_editor_status = EditorStatus::Interrupted; + printf ("^C\n"); + return 0; + + case lldb::eConnectionStatusSuccess: // Success + break; + + case lldb::eConnectionStatusError: // Check GetError() for details + case lldb::eConnectionStatusTimedOut: // Request timed out + case lldb::eConnectionStatusEndOfFile: // End-of-file encountered + case lldb::eConnectionStatusNoConnection: // No connection + case lldb::eConnectionStatusLostConnection: // Lost connection while connected to a valid connection + m_editor_status = EditorStatus::EndOfInput; + return 0; } } } - else - { - error.SetErrorString("the EditLine instance has been deleted"); - } - return error; } +const char * +Editline::Prompt() +{ + if (m_color_prompts) m_needs_prompt_repaint = true; + return m_current_prompt.c_str(); +} -Error -Editline::GetLine(std::string &line, bool &interrupted) +unsigned char +Editline::BreakLineCommand (int ch) { - Error error; - interrupted = false; - line.clear(); + // 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(""); - // 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; + // Establish the new cursor position at the start of a line when inserting a line break + m_revert_cursor_index = 0; - if (!m_got_eof) + // Don't perform end of input detection or automatic formatting when pasting + if (!IsInputPending (m_input_file)) { - if (m_getting_line) + // 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) { - error.SetErrorString("already getting a line"); - return error; + bool end_of_input = true; + if (m_is_input_complete_callback) { + SaveEditedLine(); + auto lines = GetInputAsStringList(); + end_of_input = m_is_input_complete_callback (this, lines, m_is_input_complete_callback_baton); + + // The completion test is allowed to change the input lines when complete + if (end_of_input) + { + m_input_lines.clear(); + for (unsigned index = 0; index < lines.GetSize(); index++) + { +#if LLDB_EDITLINE_USE_WCHAR + m_input_lines.insert (m_input_lines.end(), m_utf8conv.from_bytes (lines[index])); +#else + m_input_lines.insert (m_input_lines.end(), lines[index]); +#endif + } + } + } + if (end_of_input) + { + fprintf (m_output_file, "\n"); + m_editor_status = EditorStatus::Complete; + return CC_NEWLINE; + } } - if (m_lines_curr_line > 0) - { - error.SetErrorString("already getting lines"); - return error; + + // 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); } - m_getting_line = true; - error = PrivateGetLine(line); - m_getting_line = false; } + + // 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; +} - interrupted = m_interrupted; +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; + } - if (m_got_eof && line.empty()) + // 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) { - // Only set the error if we didn't get an error back from PrivateGetLine() - if (error.Success()) - error.SetErrorString("end of file"); + if (ch == 4 && info->buffer == info->lastchar) + { + fprintf (m_output_file, "^D\n"); + m_editor_status = EditorStatus::EndOfInput; + return CC_EOF; + } + return CC_ERROR; } - - return 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; } -size_t -Editline::Push (const char *bytes, size_t len) +unsigned char +Editline::DeletePreviousCharCommand (int ch) { - if (m_editline) - { - // Must NULL terminate the string for el_push() so we stick it - // into a std::string first - ::el_push(m_editline, - const_cast(std::string (bytes, len).c_str())); - return len; + 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; } - return 0; + + // 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; } - -Error -Editline::GetLines(const std::string &end_line, StringList &lines, bool &interrupted) +unsigned char +Editline::PreviousLineCommand (int ch) { - Error error; - interrupted = false; - if (m_getting_line) - { - error.SetErrorString("already getting a line"); - return error; + SaveEditedLine(); + + if (m_current_line_index == 0) { + return RecallHistory (true); } - if (m_lines_curr_line > 0) - { - error.SetErrorString("already getting lines"); - return error; + + // 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); } - // 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; - - LineStatus line_status = LineStatus::Success; + 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; +} - lines.Clear(); +unsigned char +Editline::NextLineCommand (int ch) +{ + SaveEditedLine(); - FILE *out_file = GetOutputFile(); - FILE *err_file = GetErrorFile(); - m_lines_curr_line = 1; - while (line_status != LineStatus::Done) - { - const uint32_t line_idx = m_lines_curr_line-1; - if (line_idx >= lines.GetSize()) - lines.SetSize(m_lines_curr_line); - m_lines_max_line = lines.GetSize(); - m_lines_command = Command::None; - assert(line_idx < m_lines_max_line); - std::string &line = lines[line_idx]; - error = PrivateGetLine(line); - if (error.Fail()) - { - line_status = LineStatus::Error; + // 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); } - else if (m_interrupted) + + // Determine indentation for the new line + int indentation = 0; + if (m_fix_indentation_callback) { - interrupted = true; - line_status = LineStatus::Done; + StringList lines = GetInputAsStringList(); + lines.AppendString(""); + indentation = m_fix_indentation_callback (this, lines, 0, m_fix_indentation_callback_baton); } - else - { - switch (m_lines_command) - { - case Command::None: - if (m_line_complete_callback) - { - line_status = m_line_complete_callback (this, - lines, - line_idx, - error, - m_line_complete_callback_baton); - } - else if (line == end_line) - { - line_status = LineStatus::Done; - } + 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; +} - 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(lines[line_idx+1].c_str())); - } - else if (line_status == LineStatus::Error) - { - // Clear to end of line ("ESC[K"), then print the error, - // then go to the next line ("\n") and then move cursor up - // two lines ("ESC[2A"). - fprintf (err_file, "\033[Kerror: %s\n\033[2A", error.AsCString()); - } - break; - case Command::EditPrevLine: - if (m_lines_curr_line > 1) - { - //::fprintf (out_file, "\033[1A\033[%uD\033[2K", (uint32_t)(m_lines_prompt.size() + lines[line_idx].size())); // Make cursor go up a line and clear that line - ::fprintf (out_file, "\033[1A\033[1000D\033[2K"); - if (!lines[line_idx-1].empty()) - ::el_push (m_editline, - const_cast(lines[line_idx-1].c_str())); - --m_lines_curr_line; - } - break; - case Command::EditNextLine: - // Allow the down arrow to create a new line - ++m_lines_curr_line; - //::fprintf (out_file, "\033[1B\033[%uD\033[2K", (uint32_t)(m_lines_prompt.size() + lines[line_idx].size())); - ::fprintf (out_file, "\033[1B\033[1000D\033[2K"); - if (line_idx+1 < lines.GetSize() && !lines[line_idx+1].empty()) - ::el_push (m_editline, - const_cast(lines[line_idx+1].c_str())); - break; - } - } +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()); } - m_lines_curr_line = 0; - m_lines_command = Command::None; + 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; +} - // 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::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; +} - return error; +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::HandleCompletion (int ch) +Editline::BufferEndCommand (int ch) { - if (m_completion_callback == NULL) - return CC_ERROR; + SaveEditedLine(); + MoveCursor (CursorLocation::EditingCursor, CursorLocation::BlockEnd); + SetCurrentLine ((int)m_input_lines.size() - 1); + MoveCursor (CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + return CC_NEWLINE; +} - const LineInfo *line_info = ::el_line(m_editline); +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; } -FILE * -Editline::GetInputFile () +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) { - return GetFilePointer (m_editline, 0); + // Get a shared history instance + m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name; + m_history_sp = EditlineHistory::GetHistory (m_editor_name); +} + +Editline::~Editline() +{ + if (m_editline) { + // Disable edit mode to stop the terminal from flushing all input + // during the call to el_end() since we expect to have multiple editline + // instances in this program. + el_set (m_editline, EL_EDITMODE, 0); + el_end (m_editline); + m_editline = nullptr; + } + + // EditlineHistory objects are sometimes shared between multiple + // Editline instances with the same program name. So just release + // our shared pointer and if we are the last owner, it will save the + // history to the history save file automatically. + m_history_sp.reset(); } -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; +} + +void +Editline::TerminalSizeChanged() +{ + if (m_editline != nullptr) { + el_resize (m_editline); + int columns; + // Despite the man page claiming non-zero indicates success, it's actually zero + if (el_get (m_editline, EL_GETTC, "co", &columns) == 0) { + m_terminal_width = columns; + if (m_current_line_rows != -1) { + const LineInfoW * info = el_wline (m_editline); + int lineLength = (int)((info->lastchar - info->buffer) + GetPromptWidth()); + m_current_line_rows = (lineLength / columns) + 1; + } + } + else { + m_terminal_width = INT_MAX; + m_current_line_rows = 1; + } + } } const char * Editline::GetPrompt() { - if (m_prompt_with_line_numbers && m_lines_curr_line > 0) + return m_set_prompt.c_str(); +} + +uint32_t +Editline::GetCurrentLine() +{ + return m_current_line_index; +} + +void +Editline::Hide() +{ + // Make sure we're at a stable location waiting for input + while (m_editor_status == EditorStatus::Editing && !m_editor_getting_char) { - StreamString strm; - strm.Printf("%3u: ", m_lines_curr_line); - m_lines_prompt = std::move(strm.GetString()); - return m_lines_prompt.c_str(); + usleep(100000); } - else + + // Clear the existing input + if (m_editor_status == EditorStatus::Editing) { - return m_prompt.c_str(); + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart); + fprintf(m_output_file, ANSI_CLEAR_BELOW); } } void -Editline::SetPrompt (const char *p) +Editline::Refresh() { - 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) + if (m_editor_status == EditorStatus::Editing) { - m_prompt.insert(escape_pos, 1, k_prompt_escape_char); - start_pos += 2; + DisplayInput(); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor); } } -FILE * -Editline::GetFilePointer (::EditLine *e, int fd) -{ - FILE *file_ptr = NULL; - if (e && ::el_get(e, EL_GETFP, fd, &file_ptr) == 0) - return file_ptr; - return NULL; -} - -unsigned char -Editline::CallbackEditPrevLine (::EditLine *e, int ch) +bool +Editline::Interrupt() { - Editline *editline = GetClientData (e); - if (editline->m_lines_curr_line > 1) + if (m_editor_status == EditorStatus::Editing) { - editline->m_lines_command = Command::EditPrevLine; - return CC_NEWLINE; + return m_input_connection.InterruptRead(); } - return CC_ERROR; + return false; // Interrupt not handled as we weren't getting a line or lines } -unsigned char -Editline::CallbackEditNextLine (::EditLine *e, int ch) + +void +Editline::SetAutoCompleteCallback (CompleteCallbackType callback, void * baton) { - Editline *editline = GetClientData (e); - if (editline->m_lines_curr_line < editline->m_lines_max_line) - { - editline->m_lines_command = Command::EditNextLine; - return CC_NEWLINE; - } - return CC_ERROR; + m_completion_callback = callback; + m_completion_callback_baton = baton; } -unsigned char -Editline::CallbackComplete (::EditLine *e, int ch) +void +Editline::SetIsInputCompleteCallback (IsInputCompleteCallbackType callback, void * baton) { - Editline *editline = GetClientData (e); - if (editline) - return editline->HandleCompletion (ch); - return CC_ERROR; + m_is_input_complete_callback = callback; + m_is_input_complete_callback_baton = baton; } -const char * -Editline::GetPromptCallback (::EditLine *e) +bool +Editline::SetFixIndentationCallback (FixIndentationCallbackType callback, + void * baton, + const char * indent_chars) { - Editline *editline = GetClientData (e); - if (editline) - return editline->GetPrompt(); - return ""; + m_fix_indentation_callback = callback; + m_fix_indentation_callback_baton = baton; + m_fix_indentation_callback_chars = indent_chars; + return false; } -int -Editline::GetCharFromInputFileCallback (EditLine *e, char *c) +bool +Editline::GetLine (std::string &line, bool &interrupted) { - Editline *editline = GetClientData (e); - if (editline && editline->m_got_eof == false) + ConfigureEditor (false); + m_input_lines = std::vector(); + 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) { - FILE *f = editline->GetInputFile(); - if (f == NULL) + if (input == nullptr) { - editline->m_got_eof = true; - return 0; + fprintf (m_output_file, "\n"); + m_editor_status = EditorStatus::EndOfInput; } - - - while (1) + else { - 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; - } - } + 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 0; + return m_editor_status != EditorStatus::EndOfInput; } -void -Editline::Hide () +bool +Editline::GetLines (int first_line_number, StringList &lines, bool &interrupted) { - 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)); - } - } - } -} - + 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(); + 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; -void -Editline::Refresh() -{ - if (m_getting_line) + m_revert_cursor_index = -1; + while (m_editor_status == EditorStatus::Editing) { - // 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)) - { - ::el_set (m_editline, EL_REFRESH); - } + 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()); -bool -Editline::Interrupt () -{ - 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 + lines = GetInputAsStringList(); + } + return m_editor_status != EditorStatus::EndOfInput; } diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index 8e1d080..346ddac 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -2737,13 +2737,16 @@ CommandInterpreter::HandleCommandsFromFile (FileSpec &cmd_file, 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::GetLLDBCommandsFromIOHandler (const char *prompt, { 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::GetPythonCommandsFromIOHandler (const char *prompt, { 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::RunCommandInterpreter(bool auto_handle_events, else { m_debugger.ExecuteIOHanders(); - + if (auto_handle_events) m_debugger.StopEventHandlerThread(); } - + } diff --git a/lldb/source/Interpreter/ScriptInterpreterPython.cpp b/lldb/source/Interpreter/ScriptInterpreterPython.cpp index aee0ae4..b340cc5 100644 --- a/lldb/source/Interpreter/ScriptInterpreterPython.cpp +++ b/lldb/source/Interpreter/ScriptInterpreterPython.cpp @@ -728,7 +728,7 @@ public: IOHandlerPythonInterpreter (Debugger &debugger, ScriptInterpreterPython *python) : - IOHandler (debugger), + IOHandler (debugger, IOHandler::Type::PythonInterpreter), m_python(python) { diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 0cc15e1..d6f53aa 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -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),