X-Git-Url: http://g0dil.de/git?a=blobdiff_plain;f=Utils%2FTermlib%2FEditor.hh;h=d7cadde9c1334ec630bd0e5c4e93990d509e6d52;hb=bd9f9d3fd6fbcff0112a7bf48ab9284da9576b11;hp=625342361a68a3c78305deec47656567e9001eb7;hpb=844c117cb04bc73a5b920c2c49efbf14515da3e2;p=senf.git diff --git a/Utils/Termlib/Editor.hh b/Utils/Termlib/Editor.hh index 6253423..d7cadde 100644 --- a/Utils/Termlib/Editor.hh +++ b/Utils/Termlib/Editor.hh @@ -27,6 +27,9 @@ #define HH_SENF_Utils_Termlib_Editor_ 1 // Custom includes +#include +#include +#include #include #include #include "AbstractTerminal.hh" @@ -38,6 +41,22 @@ namespace senf { namespace term { + /** \brief Provide editor support terminal functionality + + This base class utilizes an arbitrary AbstractTerminal and provides terminfo based + terminal navigation and key parsing. + + All navigation is relative to the current line. The display area can then be extended + downwards up to a maximum of height() lines. Resetting this extended display area will + return to a one line area containing only the top line. + + All navigation is restricted to a width() x height() area. The last column may not be + written to since auto-margin terminals will move the cursor to the next line when writing to + that column. + + This base class calls v_keyReceived() which must be defined in a derived class whenever a + key is fully parsed. + */ class BaseEditor : public AbstractTerminal::Callbacks { @@ -50,23 +69,37 @@ namespace term { void newline(); ///< Move to beginning of a new, empty line void toColumn(unsigned c); ///< Move cursor to column \p c - void insertChar(char ch); ///< Insert \p ch at current column, shifting text right - void overwriteChar(char ch); ///< Write \p ch at current column - void deleteChar(); ///< Delete a character a current column + void put(char ch); ///< Write \p ch at current column + void put(std::string const & text); ///< Write \a text starting at current column + void clearLine(); ///< Clear current line and move cursor to first column + void setBold(); ///< Set bold char display + void setNormal(); ///< Set normal char display + void maybeClrScr(); ///< Clear screen if possible + + void toLine(unsigned l); ///< Move to relative display line \a l + void reset(); ///< Reset display area to single line unsigned currentColumn() const; ///< Return number of current column + unsigned currentLine() const; ///< Return number of current relative line + unsigned width() const; ///< Return current screen width + unsigned height() const; ///< Return current screen height + protected: + virtual bool cb_init(); ///< Called when terminal is initialized + virtual void cb_windowSizeChanged(); + ///< Called whenever the terminal window size changes + +#ifndef DOXYGEN private: - virtual void v_keyReceived(keycode_t key) = 0; +#endif + virtual void v_keyReceived(keycode_t key) = 0; ///< Called whenever a key is received - virtual void cb_init(); + private: virtual void cb_charReceived(char c); - virtual void cb_windowSizeChanged(); void keySequenceTimeout(); void processKeys(); - unsigned width(); void write(char ch); void write(std::string const & s); @@ -77,7 +110,257 @@ namespace term { ClockService::clock_type keyTimeout_; scheduler::TimerEvent timer_; unsigned column_; + unsigned displayHeight_; + unsigned line_; }; + + /** \brief Single line interactive text editor + + LineEditor implements a single-line input widget on an arbitrary AbstractTerminal. + + \li It supports all the customary editing functions + \li It is possible to arbitrarily assign functions to keys via a key map + \li LineEditor has builtin TAB completion support + \li The LineEditor has a built-in history + \li The LineEditor supports an arbitrary auxiliary display area below the input line + \li The LineEditor has hide() / show() support to allow editing to be temporarily + interrupted. + + The LineEditor will query the user for an input line. When the user accepts a line, + LineEditor will call a user callback function. After the callback has been called, the + editor is disabled. To accept a new input line, call show(). + + \section editor_keymap The editor key map + + Keys are defined in the keymap using defineKey(). The default bindings are: + + + + + + + + + + + + + + + + + +
\c Return bindings::accept
\c Right bindings::forwardChar
\c Left bindings::backwardChar
\c Up bindings::prevHistory
\c Down bindings::nextHistory
\c Backspace bindings::backwardDeleteChar
\c Delete bindings::deleteChar
\c Home bindings::beginningOfLine
\c End bindings::endOfLine
\c Ctrl-K bindings::deleteToEndOfLine
\c Ctrl-A bindings::beginningOfLine
\c Ctrl-E bindings::endOfLine
\c Ctrl-D bindings::deleteChar
\c Ctrl-C bindings::restartEdit
\c Ctrl-L bindings::clearScreen
+ + See the senf::term::bindings namespace for a list of all default provided key binding + functions. + + + \section editor_complete Completion suppoprt + + Completion support is provided by senf::term::bindings::complete(). To use the completer, + you need to implement a completion function and pass it as second argument to + bindings::complete(): + + \code + void myCompleter(senf::term::LineEditor & editor, unsigned & b, unsigned & e, + std::string & prefix, std::vector & completions) + { + // Get text to complete + std::string text (editor.text().substr(b, e-b)); + + // Return possible completions in 'completions' array + completions.push_back( ... ); + } + + senf::term::LineEditor editor (...); + editor.defineKey(senf::term::KeyParser::TAB, + boost::bind(&senf::term::bindings::complete, _1, &myCompleter)); + \endcode + + When \c myCompleter is a class member, use senf::membind() and pass this instead of \c + &myCompleter to \c boost::bind() and thus to senf::term::bindings::complete. + + The completion protocol is as follows: When completion is desired, the completer function is + called. \a b and \a e are set to 0 and editor.point() respectively. \a prefix and + \a completions are empty. + + \li the completer may restrict the to-be-completed string to any subrange by changing \a b + and \a e accordingly. + \li If there is an initial substring which applies to \e all completions but should not be + listed in the list of completions, assign this value to \a prefix. + \li Add all possible completions to the \a completions vector not including the \a prefix. + \li The completion result is taken from the size of the \a completions vector \e only: If + this vector is empty, completion failed (even if \a prefix is set), a single entry in \a + completions (even if it is the empty string) signals a unique completion. + + + \section editor_auxarea The aux display area + + The auxiliary display area is accessed using auxDisplay() and clearAuxDisplay(). The aux + display area is \e cleared \e before each new key is processed. Therefore it is only + temporary. The aux display area however will survive hide() / show(). + + + \section editor_hideshow Temporarily disabling the editor + + Calling hide() will temporarily disable the editor. All editor display will be + removed. Calling show() will redisplay the editor in it's current state including the aux + display area. + */ + class LineEditor + : public BaseEditor + { + public: + /////////////////////////////////////////////////////////////////////////// + // Types + + typedef boost::function KeyBinding; ///< Type of a key binding function + typedef boost::function AcceptCallback; + ///< Callback function type + + static unsigned const MAX_HISTORY_SIZE = 1024u; + + /////////////////////////////////////////////////////////////////////////// + + LineEditor(AbstractTerminal & terminal, AcceptCallback cb); + ///< Create a LineEditor + /**< \parm[in] terminal abstract terminal interface + \parm[in] cb callback to call for complete input + line */ + + /////////////////////////////////////////////////////////////////////////// + + ///\name Overall edit control + ///\{ + + void show(); ///< Enable editor widget + void hide(); ///< Disable editor widget + void accept(); ///< Accept current user input and call the accept callback + void clear(); ///< Clear editor buffer + void redisplay(); ///< Mark the editor buffer for redisplay + void forceRedisplay(); ///< Redisplay the editor buffer \e now + void prompt(std::string const & text); ///< Set prompt string + + ///\} + + ///\name Cursor and display movement + ///\{ + + void gotoChar(unsigned n); ///< Move cursor to position \a n + void scrollTo(unsigned n); ///< Move positon \n to beginning of display line + + ///\} + + ///\name Text manipulation + ///\{ + + void deleteChar(unsigned n=1); ///< Delete \a n characters at point + void insert(char ch); ///< Insert \a ch at point + void insert(std::string const & text); ///< Insert \a text at point + void set(std::string const & text, unsigned pos = 0u); + ///< Set edit buffer contents + /**< The edit buffer contents will be replaced by \a + text. The cursor will be placed at position \a pos + within this text. */ + + ///\} + + ///\name History + ///\{ + + void pushHistory(std::string const & text, bool accept = false); + ///< Add string \a text to history + void prevHistory(); ///< Switch to previous history entry + void nextHistory(); ///< Switch to next history entry + + ///\} + + ///\name Aux Display + ///\{ + + void auxDisplay(unsigned line, std::string const & text); + ///< Display \a text on aux display line \a lilne + unsigned maxAuxDisplayHeight(); ///< Get maximum height of the aux display area + void clearAuxDisplay(); ///< Clear the aux display area + + ///\} + + ///\name Get information + ///\{ + + std::string const & text(); ///< Get current editor buffer contents + unsigned point(); ///< Get current cursor position + unsigned displayPos(); ///< Get current display position + keycode_t lastKey(); ///< Get last key code received + + ///\} + + ///\name Key bindings + ///\{ + + void defineKey(keycode_t key, KeyBinding binding); + ///< Bind key \a key to \a binding + void unsetKey(keycode_t key); ///< Remove all bindings for \a key + + ///\} + + private: + virtual bool cb_init(); + virtual void cb_windowSizeChanged(); + virtual void v_keyReceived(keycode_t key); + + typedef std::map KeyMap; + typedef std::vector History; + typedef std::vector AuxDisplay; + + bool enabled_; + bool redisplayNeeded_; + std::string prompt_; + unsigned promptWidth_; + unsigned editWidth_; + std::string text_; + unsigned point_; + unsigned displayPos_; + keycode_t lastKey_; + AcceptCallback callback_; + KeyMap bindings_; + History history_; + unsigned historyPoint_; + AuxDisplay auxDisplay_; + }; + +/** \brief LineEditor key bindings + */ +namespace bindings { + + void selfInsertCommand (LineEditor & editor); ///< Insert key as literal character + void forwardChar (LineEditor & editor); ///< Move one char forward + void backwardChar (LineEditor & editor); ///< Move one char backwards + void accept (LineEditor & editor); ///< Accept input line + void acceptWithRepeat (LineEditor & editor); ///< Accept, possibly repeat last history entry + void backwardDeleteChar (LineEditor & editor); ///< Delete char before cursor + void deleteChar (LineEditor & editor); ///< Delete char at cursor + void beginningOfLine (LineEditor & editor); ///< Move to beginning of line + void endOfLine (LineEditor & editor); ///< Move to end of line + void deleteToEndOfLine (LineEditor & editor); ///< Delete from cursor to end of line + void restartEdit (LineEditor & editor); ///< Clear edit buffer and restart edit + void prevHistory (LineEditor & editor); ///< Move to previous history entry + void nextHistory (LineEditor & editor); ///< Move to next history entry + void clearScreen (LineEditor & editor); ///< Clear screen and redisplay editor + + typedef boost::function &)> Completer; + void complete (LineEditor & editor, Completer completer); + ///< Complete text at cursor + /**< This function calls \a completer to find the list of + possible completions for the text between \a b and \a e + (as passed to the completer). The completer must add + all possible completions to the \a completions vector. + + \see \ref editor_complete */ + +} }}