diff --git a/examples/cxx-api.cxx b/examples/cxx-api.cxx index 8104b32..fee8fa0 100644 --- a/examples/cxx-api.cxx +++ b/examples/cxx-api.cxx @@ -444,8 +444,10 @@ int main( int argc_, char** argv_ ) { rx.bind_key_internal( Replxx::KEY::DELETE, "delete_character_under_cursor" ); rx.bind_key_internal( Replxx::KEY::LEFT, "move_cursor_left" ); rx.bind_key_internal( Replxx::KEY::RIGHT, "move_cursor_right" ); - rx.bind_key_internal( Replxx::KEY::UP, "history_previous" ); - rx.bind_key_internal( Replxx::KEY::DOWN, "history_next" ); + rx.bind_key_internal( Replxx::KEY::UP, "line_previous" ); + rx.bind_key_internal( Replxx::KEY::DOWN, "line_next" ); + rx.bind_key_internal( Replxx::KEY::meta( Replxx::KEY::UP ), "history_previous" ); + rx.bind_key_internal( Replxx::KEY::meta( Replxx::KEY::DOWN ), "history_next" ); rx.bind_key_internal( Replxx::KEY::PAGE_UP, "history_first" ); rx.bind_key_internal( Replxx::KEY::PAGE_DOWN, "history_last" ); rx.bind_key_internal( Replxx::KEY::HOME, "move_cursor_to_begining_of_line" ); diff --git a/include/replxx.h b/include/replxx.h index 1dcfddc..a9e2852 100644 --- a/include/replxx.h +++ b/include/replxx.h @@ -161,8 +161,10 @@ typedef enum { REPLXX_ACTION_MOVE_CURSOR_ONE_SUBWORD_RIGHT, REPLXX_ACTION_MOVE_CURSOR_LEFT, REPLXX_ACTION_MOVE_CURSOR_RIGHT, - REPLXX_ACTION_HISTORY_NEXT, - REPLXX_ACTION_HISTORY_PREVIOUS, + REPLXX_ACTION_LINE_NEXT, + REPLXX_ACTION_LINE_PREVIOUS, + REPLXX_ACTION_HISTORY_MOVE_NEXT, + REPLXX_ACTION_HISTORY_MOVE_PREVIOUS, REPLXX_ACTION_HISTORY_FIRST, REPLXX_ACTION_HISTORY_LAST, REPLXX_ACTION_HISTORY_RESTORE, @@ -451,8 +453,8 @@ REPLXX_IMPEXP void replxx_bind_key( Replxx*, int code, key_press_handler_t handl * * Action names are the same as unique part of names of ReplxxAction enumerations * but in lower case, e.g.: an action for recalling previous history line - * is \e REPLXX_ACTION_HISTORY_PREVIOUS so action name to be used in this - * interface for the same effect is "history_previous". + * is \e REPLXX_ACTION_LINE_PREVIOUS so action name to be used in this + * interface for the same effect is "line_previous". * * \param code - handle this key-press event with following handler. * \param actionName - name of internal action to be invoked on key press. diff --git a/include/replxx.hxx b/include/replxx.hxx index ce48139..3317705 100644 --- a/include/replxx.hxx +++ b/include/replxx.hxx @@ -176,6 +176,8 @@ public: MOVE_CURSOR_ONE_SUBWORD_RIGHT, MOVE_CURSOR_LEFT, MOVE_CURSOR_RIGHT, + LINE_NEXT, + LINE_PREVIOUS, HISTORY_NEXT, HISTORY_PREVIOUS, HISTORY_FIRST, @@ -501,8 +503,8 @@ public: * * Action names are the same as names of Replxx::ACTION enumerations * but in lower case, e.g.: an action for recalling previous history line - * is \e Replxx::ACTION::HISTORY_PREVIOUS so action name to be used in this - * interface for the same effect is "history_previous". + * is \e Replxx::ACTION::LINE_PREVIOUS so action name to be used in this + * interface for the same effect is "line_previous". * * \param code - handle this key-press event with following handler. * \param actionName - name of internal action to be invoked on key press. diff --git a/src/replxx_impl.cxx b/src/replxx_impl.cxx index 4bf91c2..9cf989a 100644 --- a/src/replxx_impl.cxx +++ b/src/replxx_impl.cxx @@ -82,6 +82,8 @@ char const COMPLETE_NEXT[] = "complete_next"; char const COMPLETE_PREVIOUS[] = "complete_previous"; char const HISTORY_NEXT[] = "history_next"; char const HISTORY_PREVIOUS[] = "history_previous"; +char const LINE_NEXT[] = "line_next"; +char const LINE_PREVIOUS[] = "line_previous"; char const HISTORY_LAST[] = "history_last"; char const HISTORY_FIRST[] = "history_first"; char const HISTORY_RESTORE[] = "history_restore"; @@ -235,8 +237,10 @@ Replxx::ReplxxImpl::ReplxxImpl( FILE*, FILE*, FILE* ) _namedActions[action_names::CLEAR_SCREEN] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CLEAR_SCREEN, _1 ); _namedActions[action_names::COMPLETE_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_NEXT, _1 ); _namedActions[action_names::COMPLETE_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_PREVIOUS, _1 ); - _namedActions[action_names::HISTORY_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_NEXT, _1 ); - _namedActions[action_names::HISTORY_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_PREVIOUS, _1 ); + _namedActions[action_names::LINE_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LINE_NEXT, _1 ); + _namedActions[action_names::LINE_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LINE_PREVIOUS, _1 ); + _namedActions[action_names::HISTORY_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_NEXT, _1 ); + _namedActions[action_names::HISTORY_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_PREVIOUS, _1 ); _namedActions[action_names::HISTORY_LAST] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_LAST, _1 ); _namedActions[action_names::HISTORY_FIRST] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_FIRST, _1 ); _namedActions[action_names::HISTORY_RESTORE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_RESTORE, _1 ); @@ -300,8 +304,10 @@ Replxx::ReplxxImpl::ReplxxImpl( FILE*, FILE*, FILE* ) bind_key( Replxx::KEY::control( 'L' ), _namedActions.at( action_names::CLEAR_SCREEN ) ); bind_key( Replxx::KEY::control( 'N' ), _namedActions.at( action_names::COMPLETE_NEXT ) ); bind_key( Replxx::KEY::control( 'P' ), _namedActions.at( action_names::COMPLETE_PREVIOUS ) ); - bind_key( Replxx::KEY::DOWN + 0, _namedActions.at( action_names::HISTORY_NEXT ) ); - bind_key( Replxx::KEY::UP + 0, _namedActions.at( action_names::HISTORY_PREVIOUS ) ); + bind_key( Replxx::KEY::DOWN + 0, _namedActions.at( action_names::LINE_NEXT ) ); + bind_key( Replxx::KEY::UP + 0, _namedActions.at( action_names::LINE_PREVIOUS ) ); + bind_key( Replxx::KEY::meta( Replxx::KEY::DOWN ), _namedActions.at( action_names::HISTORY_NEXT ) ); + bind_key( Replxx::KEY::meta( Replxx::KEY::UP ), _namedActions.at( action_names::HISTORY_PREVIOUS ) ); bind_key( Replxx::KEY::meta( '<' ), _namedActions.at( action_names::HISTORY_FIRST ) ); bind_key( Replxx::KEY::PAGE_UP + 0, _namedActions.at( action_names::HISTORY_FIRST ) ); bind_key( Replxx::KEY::meta( '>' ), _namedActions.at( action_names::HISTORY_LAST ) ); @@ -352,6 +358,8 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::invoke( Replxx::ACTION action_, char32 case ( Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_RIGHT ): return ( action( MOVE_CURSOR | RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right, code ) ); case ( Replxx::ACTION::MOVE_CURSOR_LEFT ): return ( action( MOVE_CURSOR | RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_char_left, code ) ); case ( Replxx::ACTION::MOVE_CURSOR_RIGHT ): return ( action( MOVE_CURSOR | RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_char_right, code ) ); + case ( Replxx::ACTION::LINE_NEXT ): return ( action( MOVE_CURSOR | RESET_KILL_ACTION, &Replxx::ReplxxImpl::line_next, code ) ); + case ( Replxx::ACTION::LINE_PREVIOUS ): return ( action( MOVE_CURSOR | RESET_KILL_ACTION, &Replxx::ReplxxImpl::line_previous, code ) ); case ( Replxx::ACTION::HISTORY_NEXT ): return ( action( MOVE_CURSOR | RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_next, code ) ); case ( Replxx::ACTION::HISTORY_PREVIOUS ): return ( action( MOVE_CURSOR | RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_previous, code ) ); case ( Replxx::ACTION::HISTORY_FIRST ): return ( action( MOVE_CURSOR | RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_first, code ) ); @@ -966,7 +974,7 @@ int Replxx::ReplxxImpl::virtual_render( char32_t const* buffer_, int len_, int& * Refresh the user's input line: the prompt is already onscreen and is not * redrawn here screen position */ -void Replxx::ReplxxImpl::refresh_line( HINT_ACTION hintAction_ ) { +void Replxx::ReplxxImpl::refresh_line( HINT_ACTION hintAction_, bool refreshPrompt_ ) { int long long now( now_us() ); int long long duration( now - _lastRefreshTime ); if ( duration < RAPID_REFRESH_US ) { @@ -974,6 +982,12 @@ void Replxx::ReplxxImpl::refresh_line( HINT_ACTION hintAction_ ) { _refreshSkipped = true; return; } + if ( refreshPrompt_ ) + { + _terminal.jump_cursor( 0, 0 ); + _prompt.write(); + _prompt._cursorRowOffset = _prompt._extraLines; + } _refreshSkipped = false; render( hintAction_ ); handle_hints( hintAction_ ); @@ -1862,7 +1876,7 @@ int Replxx::ReplxxImpl::pos_in_line( void ) const { } // Up, recall previous line in history -Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_previous( char32_t ) { +Replxx::ACTION_RESULT Replxx::ReplxxImpl::line_previous( char32_t ) { assert( ( _pos >= 0 ) && ( _pos <= _data.length() ) ); do { if ( ! _hasNewlines ) { @@ -1888,7 +1902,7 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_previous( char32_t ) { } // Down, recall next line in history -Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_next( char32_t ) { +Replxx::ACTION_RESULT Replxx::ReplxxImpl::line_next( char32_t ) { assert( ( _pos >= 0 ) && ( _pos <= _data.length() ) ); do { if ( ! _hasNewlines ) { @@ -1919,6 +1933,14 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_next( char32_t ) { return ( history_move( false ) ); } +Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_next( char32_t ) { + return ( history_move( false ) ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_previous( char32_t ) { + return ( history_move( true ) ); +} + Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_move( bool previous_ ) { // if not already recalling, add the current line to the history list so // we don't have to special case it @@ -1934,7 +1956,7 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_move( bool previous_ ) { } _data.assign( _history.current() ); _pos = _data.length(); - refresh_line(); + refresh_line( HINT_ACTION::REGENERATE, true /* refreshPrompt */ ); return ( Replxx::ACTION_RESULT::CONTINUE ); } @@ -2006,7 +2028,7 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_jump( bool back_ ) { _history.jump( back_ ); _data.assign( _history.current() ); _pos = _data.length(); - refresh_line(); + refresh_line( HINT_ACTION::REGENERATE, true /* refreshPrompt */ ); } return ( Replxx::ACTION_RESULT::CONTINUE ); } diff --git a/src/replxx_impl.hxx b/src/replxx_impl.hxx index a5e3f15..aa7cfae 100644 --- a/src/replxx_impl.hxx +++ b/src/replxx_impl.hxx @@ -241,6 +241,8 @@ private: Replxx::ACTION_RESULT delete_character( char32_t ); Replxx::ACTION_RESULT backspace_character( char32_t ); Replxx::ACTION_RESULT commit_line( char32_t ); + Replxx::ACTION_RESULT line_next( char32_t ); + Replxx::ACTION_RESULT line_previous( char32_t ); Replxx::ACTION_RESULT history_next( char32_t ); Replxx::ACTION_RESULT history_previous( char32_t ); Replxx::ACTION_RESULT history_move( bool ); @@ -270,7 +272,7 @@ private: void call_modify_callback( void ); completions_t call_completer( std::string const& input, int& ) const; hints_t call_hinter( std::string const& input, int&, Replxx::Color& color ) const; - void refresh_line( HINT_ACTION = HINT_ACTION::REGENERATE ); + void refresh_line( HINT_ACTION = HINT_ACTION::REGENERATE, bool refreshPrompt_ = false ); void move_cursor( void ); void indent( void ); int virtual_render( char32_t const*, int, int&, int&, Prompt const* = nullptr ); diff --git a/tests.py b/tests.py index adc4c80..07ec6cb 100755 --- a/tests.py +++ b/tests.py @@ -3235,6 +3235,29 @@ def test_history_scratch( self_ ): "three\r\n", "one\ntwo\nthree\n" ) + def test_move_up_over_multiline( self_ ): + self_.check_scenario( + "", + "ZZZbbbbbbbbbbbbbbbb\r\n" + "bbbbbbbbbbbbbbbbbbbb\r\n" + "bbbbbbbbbbbbbbbbbbbb\r\n" + "bbbbbbbbbbbbbbbbbbbbbbb123123\r\n" + "123\r\n", + "123\nbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\nZZZ\n" + ) + def test_move_down_over_multiline( self_ ): + self_.check_scenario( + "x", + "123bbbbbbbbbbbbbbbbbbbbbbb\r\n" + "bbbbbbbbbbbbbbbbbbbb\r\n" + "bbbbbbbbbbbbbbbbbbbb\r\n" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n" + "bbbbbbbbbbbbbbbbbbbb\r\n" + "bbbbbbbbbbbbbbbbbbbb\r\n" + "bbbbbbbbbbbbbbbbZZZxx\r\n" + "x\r\n", + "123\nbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\nZZZ\n" + ) def parseArgs( self, func, argv ): global verbosity