|  | // Copyright 2018 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #ifndef SRC_LIB_LINE_INPUT_LINE_INPUT_H_ | 
|  | #define SRC_LIB_LINE_INPUT_LINE_INPUT_H_ | 
|  |  | 
|  | #include <deque> | 
|  | #include <map> | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | #include "lib/fit/function.h" | 
|  |  | 
|  | #if !defined(__Fuchsia__) | 
|  | struct termios; | 
|  | #endif | 
|  |  | 
|  | namespace line_input { | 
|  |  | 
|  | struct SpecialCharacters { | 
|  | static constexpr char kKeyControlA = 1; | 
|  | static constexpr char kKeyControlB = 2; | 
|  | static constexpr char kKeyControlC = 3; | 
|  | static constexpr char kKeyControlD = 4; | 
|  | static constexpr char kKeyControlE = 5; | 
|  | static constexpr char kKeyControlF = 6; | 
|  | static constexpr char kKeyControlH = 8; | 
|  | static constexpr char kKeyTab = 9; | 
|  | static constexpr char kKeyNewline = 10; | 
|  | static constexpr char kKeyControlK = 11; | 
|  | static constexpr char kKeyFormFeed = 12; | 
|  | static constexpr char kKeyEnter = 13; | 
|  | static constexpr char kKeyControlN = 14;  // Down | 
|  | static constexpr char kKeyControlP = 16;  // Up | 
|  | static constexpr char kKeyControlR = 18; | 
|  | static constexpr char kKeyControlT = 20; | 
|  | static constexpr char kKeyControlU = 21; | 
|  | static constexpr char kKeyControlW = 23; | 
|  | static constexpr char kKeyEsc = 27; | 
|  | static constexpr char kKeyBackspace = 127; | 
|  |  | 
|  | // Escape sequences for terminal output. | 
|  | static const char* kTermBeginningOfLine; | 
|  | static const char* kTermClearToEnd; | 
|  | static const char* kTermCursorToColFormat;  // printf format. | 
|  | }; | 
|  |  | 
|  | // Abtract base class for line input. | 
|  | // | 
|  | // This class implements a push model for input of characters, allowing it to be used in | 
|  | // asynchronous contexts. | 
|  | // | 
|  | // The model is you create a LineInput class outside of the input loop. It encapsulates the history | 
|  | // state and remembers the prompt. When you want to read a line: | 
|  | // | 
|  | //  1. Call Show(). | 
|  | //  2. Push data to it via OnInput(). | 
|  | //  3. On an accept callback: | 
|  | //     3a. Handle the input. | 
|  | //     3b. Add line to history if desired. | 
|  | //  4. Repeat until done. | 
|  | //  5. Call Hide() to put the terminal back. | 
|  | // | 
|  | // If your application has data that it needs to print asynchronously, just: | 
|  | //  1. Call Hide(). | 
|  | //  2. Print the stuff you want. | 
|  | //  3. Call Show(). | 
|  | class LineInput { | 
|  | public: | 
|  | virtual ~LineInput() = default; | 
|  |  | 
|  | // Called with the user input when the user acceps a line. This takes a copy because the accept | 
|  | // callback can often modify the command history which in turn owns the string being edited and | 
|  | // accepted. | 
|  | using AcceptCallback = fit::function<void(std::string)>; | 
|  |  | 
|  | // Called when the current line changes. This is not called for <Enter> which doesn't change | 
|  | // anything but will call the AcceptCallback. As in AcceptCallback, this takes a copy. | 
|  | using ChangeCallback = fit::function<void(std::string)>; | 
|  |  | 
|  | // Given some typing, returns a prioritized list of completions. | 
|  | using AutocompleteCallback = fit::function<std::vector<std::string>(const std::string&)>; | 
|  |  | 
|  | // Callback that indicates Control-C or EOF (Control-D) was typed. | 
|  | using CancelCallback = fit::function<void()>; | 
|  | using EofCallback = fit::function<void()>; | 
|  |  | 
|  | // Setup ----------------------------------------------------------------------------------------- | 
|  |  | 
|  | // Provides the callback for tab completion. | 
|  | virtual void SetAutocompleteCallback(AutocompleteCallback cb) = 0; | 
|  |  | 
|  | // Provides the callback for when the current line changes. | 
|  | virtual void SetChangeCallback(ChangeCallback cb) = 0; | 
|  |  | 
|  | // Provides the callback for handling Control-C. If unset, "^C" will be echoed and the line | 
|  | // will be cleared. | 
|  | virtual void SetCancelCallback(CancelCallback cb) = 0; | 
|  |  | 
|  | // Provides the callback for handling EOF. If unset EOF will be ignored. | 
|  | virtual void SetEofCallback(EofCallback cb) = 0; | 
|  |  | 
|  | // Sets the maximum width of a line. Beyond this the input will scroll. Setitng to 0 will disable | 
|  | // horizontal scrolling. | 
|  | virtual void SetMaxCols(size_t max) = 0; | 
|  |  | 
|  | // Querying -------------------------------------------------------------------------------------- | 
|  |  | 
|  | // Returns the current input text. | 
|  | virtual const std::string& GetLine() const = 0; | 
|  |  | 
|  | // Returns the current history. The most resent input is at the begin(). | 
|  | virtual const std::deque<std::string>& GetHistory() const = 0; | 
|  |  | 
|  | // State ----------------------------------------------------------------------------------------- | 
|  |  | 
|  | // Provides one character of input to the editor. Callbacks for autocomplete or line done will be | 
|  | // issued from within this function. | 
|  | virtual void OnInput(char c) = 0; | 
|  |  | 
|  | // Adds the given line to history. If the history is longer than max_history_, the oldest thing | 
|  | // will be deleted. | 
|  | // | 
|  | // AddToHistory should be called on startup before the initial Show() call, or from within the | 
|  | // accept callback (typically you would add the current line at this point). | 
|  | virtual void AddToHistory(const std::string& line) = 0; | 
|  |  | 
|  | // Whether the input should handle interruptions (e.g., handle Ctrl-C by calling the cancel | 
|  | // callback and handle Ctrl-D by calling EOF callbacks) while hidden. | 
|  | enum class InterruptHandlingBehavior { | 
|  | // Handle input interuptions in-band, such as Ctrl-C and Ctrl-D. | 
|  | kHandleInterrupts, | 
|  |  | 
|  | // Ignore input interruptions, for example letting the pty route Ctrl-C to the shell. | 
|  | kIgnoreInterrupts, | 
|  | }; | 
|  |  | 
|  | // The input can be hidden and re-shown. Hiding it will erase the current line and put the cursor | 
|  | // at the beginning of the line, but not change any internal state. Showing it again will repaint | 
|  | // the line at the new cursor position. This allows other output to be printed to the screen | 
|  | // without interfering with the input. | 
|  | // | 
|  | // Hiding or showing when it's already in that state will do nothing. There is not a reference | 
|  | // count on the hide calls. | 
|  | // | 
|  | // OnInput() should not be called while hidden. | 
|  | // | 
|  | // Tip: When the application is done (the user types "quit" or whatever), call Hide() from within | 
|  | // the AcceptCallback or EofCallback. This will ensure the prompt isn't repainted when the | 
|  | // callback is complete only to be rehidden on exit (which will cause flickering). | 
|  | virtual void Hide( | 
|  | InterruptHandlingBehavior behavior = InterruptHandlingBehavior::kHandleInterrupts) = 0; | 
|  | virtual void Show() = 0; | 
|  |  | 
|  | // Replaces the contents of the current line with the given contents. The cursor will be placed | 
|  | // at the end of the text. Any changes in editing history will be reset. | 
|  | // | 
|  | // This bypasses handling of any special characters and input and any such | 
|  | // characters will be included as literals on the line. This will issue any changed callbacks. | 
|  | virtual void SetCurrentInput(const std::string& input) = 0; | 
|  | }; | 
|  |  | 
|  | // Implementation of LineInput that implements the editing state. Output is still abstract to | 
|  | // allow for output to different places. | 
|  | class LineInputEditor : public LineInput { | 
|  | public: | 
|  | explicit LineInputEditor(AcceptCallback accept_cb, const std::string& prompt); | 
|  | virtual ~LineInputEditor(); | 
|  |  | 
|  | // LineInput implementation. | 
|  | void SetAutocompleteCallback(AutocompleteCallback cb) override; | 
|  | void SetChangeCallback(ChangeCallback cb) override; | 
|  | void SetCancelCallback(CancelCallback cb) override; | 
|  | void SetEofCallback(EofCallback cb) override; | 
|  | void SetMaxCols(size_t max) override; | 
|  | const std::string& GetLine() const override; | 
|  | const std::deque<std::string>& GetHistory() const override; | 
|  | void OnInput(char c) override; | 
|  | void AddToHistory(const std::string& line) override; | 
|  | void Hide( | 
|  | InterruptHandlingBehavior behavior = InterruptHandlingBehavior::kHandleInterrupts) override; | 
|  | void Show() override; | 
|  | void SetCurrentInput(const std::string& input) override; | 
|  |  | 
|  | size_t pos() const { return pos_; } | 
|  |  | 
|  | bool in_reverse_history_mode() const { return reverse_history_mode_; } | 
|  | size_t reverse_history_index() const { return reverse_history_index_; } | 
|  |  | 
|  | // Exposed for testing purposes. | 
|  | std::string GetReverseHistoryPrompt() const; | 
|  | std::string GetReverseHistorySuggestion() const; | 
|  |  | 
|  | protected: | 
|  | enum TerminalMode { | 
|  | // The original mode of the terminal when we started. Used for putting it back when we exit. | 
|  | kOriginalMode, | 
|  |  | 
|  | // Raw input and output. Used for active line input. | 
|  | kRawInOutMode, | 
|  |  | 
|  | // Raw input only. Output will be formatted as normal in a terminal. Used when the input is | 
|  | // hidden so the app may print with normal behavior but input is still raw. This prevents | 
|  | // echoing input to the terminal when we're not processing it, and Control-C from exiting the | 
|  | // app out from under us. | 
|  | kRawInMode, | 
|  | }; | 
|  |  | 
|  | // Sets the terminal mode to the given value if necessary. | 
|  | virtual void EnsureTerminalMode(TerminalMode mode) {} | 
|  |  | 
|  | // Abstract output function, overridden by a derived class to output to screen. | 
|  | virtual void Write(const std::string& data) = 0; | 
|  |  | 
|  | // Helper to return the current line of text as an editable string. This uses the overlay provided | 
|  | // by editing_history and falls back to promoting the value from persistent_history_. | 
|  | // | 
|  | // Use the const GetLine() on the base class to avoid the promotion if the returned value will | 
|  | // not be modified. | 
|  | std::string& mutable_cur_line() { | 
|  | if (auto found = editing_history_.find(history_index_); found != editing_history_.end()) | 
|  | return found->second; | 
|  |  | 
|  | // Make the permanent history line mutable by copying to the editing history. | 
|  | return editing_history_[history_index_] = persistent_history_[history_index_]; | 
|  | } | 
|  |  | 
|  | // Useful for testing. | 
|  | void set_pos(size_t pos) { pos_ = pos; } | 
|  |  | 
|  | const std::string& prompt() const { return prompt_; } | 
|  | void set_prompt(std::string prompt) { prompt_ = std::move(prompt); } | 
|  |  | 
|  | private: | 
|  | void HandleEscapedInput(char c); | 
|  |  | 
|  | void HandleBackspace(); | 
|  | void HandleDelete(); | 
|  | // FormFeed is the name of Ctrl-L in ASCII world. | 
|  | void HandleFormFeed(); | 
|  | void HandleEnter(); | 
|  | void HandleTab(); | 
|  | // NegAck is the name of Ctrl-U in ASCII world. | 
|  | void HandleNegAck(); | 
|  | // EndOfTransimission is the name for Ctrl-W in ASCII world. | 
|  | void HandleEndOfTransimission(); | 
|  | // EndOfFile means Ctrl-D with an empty input line. | 
|  | void HandleEndOfFile(); | 
|  |  | 
|  | // ReverseHistory means Ctrl-R. | 
|  | void HandleReverseHistory(char c); | 
|  | void StartReverseHistoryMode(); | 
|  | void EndReverseHistoryMode(bool accept_suggestion); | 
|  | void SearchNextReverseHistory(bool restart); | 
|  |  | 
|  | void Insert(char c); | 
|  | void MoveLeft(); | 
|  | void MoveRight(); | 
|  | void MoveUp(); | 
|  | void MoveDown(); | 
|  | void MoveHome(); | 
|  | void MoveEnd(); | 
|  |  | 
|  | void TransposeLastTwoCharacters(); | 
|  | void CancelCommand(); | 
|  | void DeleteToEnd(); | 
|  |  | 
|  | void CancelCompletion(); | 
|  | void AcceptCompletion(); | 
|  |  | 
|  | // Issues a line change notification and repaints the current line. | 
|  | void LineChanged(); | 
|  |  | 
|  | // Repaints the current line without issuing a line change notification. | 
|  | void RepaintLine(); | 
|  |  | 
|  | void ResetLineState(); | 
|  |  | 
|  | AcceptCallback accept_callback_; | 
|  | ChangeCallback change_callback_;  // Possibly null. | 
|  | std::string prompt_; | 
|  |  | 
|  | size_t max_cols_ = 0; | 
|  | AutocompleteCallback autocomplete_callback_;  // Possibly null. | 
|  | CancelCallback cancel_callback_;              // Possibly null;. | 
|  | EofCallback eof_callback_;                    // Possibly null;. | 
|  |  | 
|  | // Indicates whether the line is currently visible (as controlled by Show()/Hide()). | 
|  | bool visible_ = false; | 
|  |  | 
|  | // The history is basically the line stack going back in time as indices increase. There are two | 
|  | // versions of history: the permanent history of stuff the user has typed, and the editing history | 
|  | // which is where temporary modifications are made until something is accepted. | 
|  | // | 
|  | // The editing history shadows the permanent history so the user can move up and down and edit | 
|  | // different items in history, seeing those edits as they move up and down. But when a new history | 
|  | // item is added, those edits are all cleared and the history represents what was actually input | 
|  | // at each point. Only the line that was input is kept, and that's saved only by adding it to the | 
|  | // front of history. This temporary shadow copy of history where edits occur matches the behavior | 
|  | // of other popular line input libraries. | 
|  | // | 
|  | // Use GetLine() to get a const view of the current line.  Use mutable_cur_line() to get a mutable | 
|  | // version of the current line (promotes to editable). Avoid changing the strings in these | 
|  | // structures outside of mutable_cur_line(). | 
|  | std::deque<std::string> persistent_history_;  // front() is newest. | 
|  | size_t history_index_ = 0;                    // Offset from persistent_history_.front(). | 
|  | const size_t max_history_ = 256; | 
|  | std::map<size_t, std::string> editing_history_;  // Indices override items in persistent_history_. | 
|  |  | 
|  | bool completion_mode_ = false; | 
|  | std::vector<std::string> completions_; | 
|  | size_t completion_index_ = 0; | 
|  |  | 
|  | // Tracks the current line's state before suggesting completions so we can | 
|  | // put them back if necessary. Only valid when completion_mode_ = true. | 
|  | std::string line_before_completion_; | 
|  | size_t pos_before_completion_ = 0; | 
|  |  | 
|  | // When an escape is read, we enter "escaped input" mode which interprets the | 
|  | // next few characters input as an escape sequence. | 
|  | bool reading_escaped_input_ = false; | 
|  | std::string escape_sequence_; | 
|  |  | 
|  | bool reverse_history_mode_ = false; | 
|  | std::string reverse_history_input_; | 
|  |  | 
|  | // Index within history the reverse search suggestion current is. 0 means not found, as that is | 
|  | // pointing to the current line, which we don't want to do a history search in. | 
|  | size_t reverse_history_index_ = 0; | 
|  |  | 
|  | size_t pos_ = 0;  // Current editing position. | 
|  | }; | 
|  |  | 
|  | // Implementation of LineInput that prints to stdout. The caller is still responsible for providing | 
|  | // input asynchronously. The initial width of the output will be automatically derived from the | 
|  | // terminal associated wit, as that is pointing to the current line, which we don't want to do a | 
|  | // history search in. stdout (if any). | 
|  | class LineInputStdout : public LineInputEditor { | 
|  | public: | 
|  | LineInputStdout(AcceptCallback accept_cb, const std::string& prompt); | 
|  | ~LineInputStdout() override; | 
|  |  | 
|  | protected: | 
|  | // LineInputEditor implementation. | 
|  | void EnsureTerminalMode(TerminalMode mode) override; | 
|  | void Write(const std::string& str) override; | 
|  |  | 
|  | private: | 
|  | #if !defined(__Fuchsia__) | 
|  | // The terminal is converted into raw mode when the prompt is visible and accepting input. Then | 
|  | // it's switched back. This block tracks that information. Use unique_ptr to avoid bringing all | 
|  | // terminal headers into this header. | 
|  | TerminalMode terminal_mode_ = kOriginalMode; | 
|  | std::unique_ptr<termios> original_termios_; | 
|  | std::unique_ptr<termios> raw_inout_termios_; | 
|  | std::unique_ptr<termios> raw_in_termios_; | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | }  // namespace line_input | 
|  |  | 
|  | #endif  // SRC_LIB_LINE_INPUT_LINE_INPUT_H_ |