/*! @file SReadline.h @brief C++ wrapper around libreadline. Supported: editing, history, custom completers, keymaps. Attention: implementation is not thread safe! It is mainly because the readline library provides pure C interface and has many calls for an "atomic" completion operation */ // // Date: 17 December 2005 // 03 April 2006 // 20 April 2006 // 07 May 2006 // // Copyright (c) Sergey Satskiy 2005 - 2006 // // // Permission to copy, use, modify, sell and distribute this software // is granted provided this copyright notice appears in all copies. // This software is provided "as is" without express or implied // warranty, and with no claim as to its suitability for any purpose. // #ifndef SREADLINE_H #define SREADLINE_H #include #include #include #include #include #include #include #include #include #include #include #include /*! @brief Used to avoid linking errors in case of including into many compilation units */ namespace { //! @brief Tokens in a single variation of a user command typedef std::vector< std::string > TokensStorage; //! @brief Set of variations of user commands typedef std::vector< TokensStorage > CompletionsStorage; //! @brief Pressed key callback. Must return 0 if OK, != 0 otherwise typedef boost::function< int ( int, int ) > KeyCallback; //! @brief A set of keys binding typedef std::map< int, KeyCallback > KeysBind; const size_t DefaultHistoryLimit( 64 ); //!< Default value for the history length CompletionsStorage Completions; //!< Global storage of custom completions TokensStorage Tokens; //!< Tokens storage for a single completion session std::map< Keymap, KeysBind > Keymaps; //!< Global storage for keymaps bool KeymapWasSetup( false ); //!< Has sense if a keymap was //!< setup before the first readline call Keymap Earlykeymap( 0 ); //!< The keymap which was setup before the first readline call /*! @brief Custom completion generator @param text Pointer to a token to be completed @param State 0 for a first call, non 0 for all consequent calls */ char * Generator( const char * text, int State ); /*! @brief The function is called before trying to complete a token @param text A token to be completed @param start Index of the beginning of the token in the readline buffer @param end Index of the end of the token in the readline buffer */ char ** UserCompletion( const char * text, int start, int end ); /*! @brief The function selects the set of bindings and makes the corresponding call. @param Count The parameter is passed by readline @param Key The pressed key */ int KeyDispatcher( int Count, int Key ); /*! @brief The readline startup hook. It is required to setup the proper keymap. */ int StartupHook( void ); /*! @brief Compares all the Input tokens with starts tokens in the Pattern @param Pattern pattern tokens @param Input user input tokens @return true if first Input.size() tokens are equal to the pattern tokens */ template < typename Container > bool AreTokensEqual( const Container & Pattern, const Container & Input ) { if ( Input.size() > Pattern.size() ) return false; typename Container::const_iterator k( Pattern.begin() ); typename Container::const_iterator j( Input.begin() ); for ( ; j != Input.end(); ++k, ++j ) { if ( *k == "%file" ) continue; if ( *k != *j ) return false; } return true; } // See description near the prototype template < typename ContainerType > void SplitTokens( const std::string & Source, ContainerType & Container ) { typedef boost::tokenizer< boost::char_separator< char > > TokenizerType; boost::char_separator Separators( " \t\n" ); // Set of token separators TokenizerType Tokenizer( Source, Separators ); // Tokens provider std::string SingleToken; // Temporary storage for a token Container.clear(); for ( TokenizerType::const_iterator k( Tokenizer.begin() ); k != Tokenizer.end(); ++k ) { SingleToken = *k; boost::algorithm::trim( SingleToken ); Container.push_back( SingleToken ); } } // See description near the prototype char ** UserCompletion( const char * text, int start, int /*end*/ ) { // No default completion at all rl_attempted_completion_over = 1; if ( Completions.empty() ) return NULL; // Memorize all the previous tokens std::string PreInput( rl_line_buffer, start ); SplitTokens( PreInput, Tokens ); // Detect whether we should call the standard file name completer or a custom one bool FoundPretender( false ); for ( CompletionsStorage::const_iterator k( Completions.begin() ); k != Completions.end(); ++k ) { if ( ! AreTokensEqual( *k, Tokens ) ) continue; if ( (*k).size() > Tokens.size() ) { FoundPretender = true; if ( (*k)[ Tokens.size() ] == "%file" ) { // Standard file name completer - called for the "%file" keyword return rl_completion_matches( text, rl_filename_completion_function ); } } } if ( FoundPretender ) { return rl_completion_matches( text, Generator ); } return NULL; } // See description near the prototype char * Generator( const char * text, int State ) { static int Length; static CompletionsStorage::const_iterator Iterator; if ( State == 0 ) { Iterator = Completions.begin(); Length = strlen( text ); } for ( ; Iterator != Completions.end(); ++Iterator ) { if ( ! AreTokensEqual( *Iterator, Tokens ) ) continue; if ( (*Iterator).size() > Tokens.size() ) { if ( (*Iterator)[ Tokens.size() ] == "%file" ) continue; if ( strncmp( text, (*Iterator)[ Tokens.size() ].c_str(), Length ) == 0 ) { // readline will free the allocated memory char * NewString( (char*)malloc( strlen( (*Iterator)[ Tokens.size() ].c_str() ) + 1 ) ); strcpy( NewString, (*Iterator)[ Tokens.size() ].c_str() ); ++Iterator; return NewString; } } } return NULL; } // See the description near the prototype int KeyDispatcher( int Count, int Key ) { std::map< Keymap, KeysBind >::iterator Set( Keymaps.find( rl_get_keymap() ) ); if ( Set == Keymaps.end() ) { // Most probably it happens bacause the header was // included into many compilation units and the // keymap setting calls were made in different files. // This is the problem of "global" data. // The storage of all the registered keymaps is in anonymous // namespace. throw std::runtime_error( "Error selecting a keymap." ); } (Set->second)[ Key ]( Count, Key ); return 0; } // See the description near the prototype int StartupHook( void ) { if ( KeymapWasSetup ) { rl_set_keymap( Earlykeymap ); } return 0; } } // Anonymous namespace /*! @brief The wrapper namespace. The namespace is also used for other library elements. */ namespace swift { /*! @brief The readline keymap wrapper. Attention: It is not thread safe! Supports: key binding, key unbinding */ class SKeymap { private: Keymap keymap; // Readline keymap public: /*! @brief Creates a new keymap @param PrintableBound if true - the printable characters are bound if false - the keymap is empty */ explicit SKeymap( bool PrintableBound = false ) : keymap( NULL ) { if ( PrintableBound ) { keymap = rl_make_keymap(); // Printable characters are bound } else { keymap = rl_make_bare_keymap(); // Empty keymap } if ( keymap == NULL ) { throw std::runtime_error( "Cannot allocate keymap." ); } // Register a new keymap in the global list Keymaps[ keymap ] = KeysBind(); } /*! @brief Creates a new keymap which is a copy of Pattern @param Pattern A keymap to be copied */ explicit SKeymap( Keymap Pattern ) : keymap( rl_copy_keymap( Pattern ) ) { if ( keymap == NULL ) { throw std::runtime_error( "Cannot allocate keymap." ); } // Register a new keymap in the global list Keymaps[ keymap ] = KeysBind(); } /*! @brief Frees the allocated keymap */ ~SKeymap() { // Deregister the keymap Keymaps.erase( keymap ); rl_discard_keymap( keymap ); } /*! @brief Binds the given key to a function @param Key A key to be bound @param Callback A function to be called when the Key is pressed */ void Bind( int Key, KeyCallback Callback ) { Keymaps[ keymap ][ Key ] = Callback; if ( rl_bind_key_in_map( Key, KeyDispatcher, keymap ) != 0 ) { // Remove from the map just bound key Keymaps[ keymap ].erase( Key ); throw std::runtime_error( "Invalid key." ); } } /*! @brief Unbinds the given key @param Key A key to be unbound */ void Unbind( int Key ) { rl_unbind_key_in_map( Key, keymap ); Keymaps[ keymap ].erase( Key ); } // void Bind( const std::string & Sequence, boost::function< int ( int, int ) > ); // void Unbind( std::string & Sequence ); public: /*! @brief Copy constructor @param rhs Right hand side object of SKeymap */ SKeymap( const SKeymap & rhs ) : keymap(0) { if ( this == & rhs ) { return; } keymap = rl_copy_keymap( rhs.keymap ); } /*! @brief operator= @param rhs Right hand side object of SKeymap */ SKeymap & operator=( const SKeymap & rhs ) { if ( this == & rhs ) { return *this; } keymap = rl_copy_keymap( rhs.keymap ); return *this; } friend class SReadline; }; /*! @brief The readline library wrapper. Attention: It is not thread safe! Supports: editing, history, custom completers */ class SReadline { public: /*! @brief Constructs the object, sets the completion function @param Limit History size */ SReadline( const size_t Limit = DefaultHistoryLimit ) : HistoryLimit( Limit ), HistoryFileName( "" ), OriginalCompletion( rl_attempted_completion_function ) { rl_startup_hook = StartupHook; rl_attempted_completion_function = UserCompletion; using_history(); } /*! @brief Constructs the object, sets the completion function, loads history @param historyFileName File name to load history from @param Limit History size */ SReadline( const std::string & historyFileName, const size_t Limit = DefaultHistoryLimit ) : HistoryLimit( Limit ), HistoryFileName( historyFileName ), OriginalCompletion( rl_attempted_completion_function ) { rl_startup_hook = StartupHook; rl_attempted_completion_function = UserCompletion; using_history(); LoadHistory( HistoryFileName ); } /*! @brief Saves the session history (if the file name was provided) and destroys the object */ ~SReadline() { rl_attempted_completion_function = OriginalCompletion; SaveHistory( HistoryFileName ); } /*! @brief Gets a single line from a user @param Prompt A printed prompt @return A string which was actually inputed */ std::string GetLine( const std::string & Prompt ) { bool Unused; return GetLine( Prompt, Unused ); } /*! @brief Gets a single line from a user @param Prompt A printed prompt @param ReadTokens A user inputed string splitted into tokens. The container is cleared first @return A string which was actually inputed */ template < typename Container > std::string GetLine( const std::string & Prompt, Container & ReadTokens ) { bool Unused; return GetLine( Prompt, ReadTokens, Unused ); } /*! @brief Gets a single line from a user @param Prompt A printed prompt @param BreakOut it is set to true if the EOF found @param ReadTokens A user inputed string splitted into tokens. The container is cleared first @return A string which was actually inputed */ template < typename Container > std::string GetLine( const std::string & Prompt, Container & ReadTokens, bool & BreakOut ) { std::string Input( GetLine( Prompt, BreakOut ) ); SplitTokens( Input, ReadTokens ); return Input; } /*! @brief Gets a single line from a user @param Prompt A printed prompt @param BreakOut it is set to true if the EOF found @return A string which was actually inputed */ std::string GetLine( const std::string & Prompt, bool & BreakOut ) { BreakOut = true; char * ReadLine( readline( Prompt.c_str() ) ); if ( ReadLine == NULL ) return std::string(); // It's OK BreakOut = false; std::string Input( ReadLine ); free( ReadLine ); boost::algorithm::trim( Input ); if ( !Input.empty() ) { if ( (history_length == 0) || (Input != history_list()[ history_length - 1 ]->line) ) { add_history( Input.c_str() ); if ( history_length >= static_cast< int >( HistoryLimit ) ) { stifle_history( HistoryLimit ); } } } return Input; } /*! @brief Fills the given container with the current history list Does not clear the given container */ template < typename ContainerType > void GetHistory( ContainerType & Container ) { for ( int k( 0 ); k < history_length; ++k ) { Container.push_back( history_list()[ k ]->line ); } } /*! @brief Saves the history to the given file stream @param OS output file stream @return true if success */ bool SaveHistory( std::ostream & OS ) { if ( ! OS ) return false; for ( int k( 0 ); k < history_length; ++k ) { OS << history_list()[ k ]->line << std::endl; } return true; } /*! @brief Saves the history to the given file @param FileName File name to save the history to @return true if success */ bool SaveHistory( const std::string & FileName ) { if ( FileName.empty() ) return false; std::ofstream OS( FileName.c_str() ); return SaveHistory( OS ); } /*! @brief Clears the history. Does not affect the file where the previous session history is saved. */ void ClearHistory() { clear_history(); } /*! @brief Loads a history from a file stream @param IS Input file stream @return true if success */ bool LoadHistory( std::istream & IS ) { if ( ! IS ) return false; ClearHistory(); std::string OneLine; while ( ! getline( IS, OneLine ).eof() ) { boost::algorithm::trim( OneLine ); if ( (history_length == 0) || (OneLine != history_list()[ history_length - 1 ]->line) ) { add_history( OneLine.c_str() ); } } stifle_history( HistoryLimit ); return true; } /*! @brief Loads a history from the given file @param FileName File name to be load from @return true if success */ bool LoadHistory( const std::string & FileName ) { if ( FileName.empty() ) return false; std::ifstream IS( FileName.c_str() ); return LoadHistory( IS ); } /*! @brief Allows to register custom completers. Supports a special keyword: %file. It means to use the standard file name completer For example the given container elements could be as follows: command1 opt1 command1 opt2 %file command2 command2 opt1 Each container element must describe a single possible command line The container element must have a conversion to std::string operator @param Container A container which has all the user possible commands */ template < typename ContainerType > void RegisterCompletions( const ContainerType & Container ) { Completions.clear(); for ( typename ContainerType::const_iterator k( Container.begin() ); k != Container.end(); ++k ) { std::vector< std::string > OneLine; SplitTokens( (*k).get<0>(), OneLine ); Completions.push_back( OneLine ); } } /*! @brief Sets the given keymap @param NewKeymap The keymap that should be used from now */ void SetKeymap( SKeymap & NewKeymap ) { rl_set_keymap( NewKeymap.keymap ); KeymapWasSetup = true; Earlykeymap = NewKeymap.keymap; } private: const size_t HistoryLimit; //!< Constructor accepted or default const std::string HistoryFileName; //!< Original constructor accepted rl_completion_func_t * OriginalCompletion; //!< Original state will be restored }; } // namespace swift #endif