From 453bd9a9a795db220c0f9016ba429fa5df46ae85 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Tue, 9 Nov 2021 14:55:33 +0800 Subject: [PATCH 1/2] Always re-render prompt while navigating history --- src/replxx_impl.cxx | 12 +++++++++--- src/replxx_impl.hxx | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/replxx_impl.cxx b/src/replxx_impl.cxx index 4bf91c2..fb62cf7 100644 --- a/src/replxx_impl.cxx +++ b/src/replxx_impl.cxx @@ -966,7 +966,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 +974,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_ ); @@ -1934,7 +1940,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 +2012,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..93f95a1 100644 --- a/src/replxx_impl.hxx +++ b/src/replxx_impl.hxx @@ -270,7 +270,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 ); From 7de81e3e4cc02dbc4c835a302b2052dcafb18d12 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 19 Nov 2021 16:18:28 +0300 Subject: [PATCH 2/2] Add history_move_{next/prevoius} Right now there is UP/DOWN arrows, that is binded to history_prevous/history_next accordingly. But in case of multiline history entry it will navigate through the lines in the current history itemfirst, and this may not be the requird behavior. So now replxx will have history_move_*, that will always navigate through history, regardless of new lines in query. New history_move_{next/prev} binded to M-UP/M-DOWN (like in readline). --- examples/cxx-api.cxx | 6 ++++-- include/replxx.h | 10 ++++++---- include/replxx.hxx | 6 ++++-- src/replxx_impl.cxx | 28 ++++++++++++++++++++++------ src/replxx_impl.hxx | 2 ++ tests.py | 23 +++++++++++++++++++++++ 6 files changed, 61 insertions(+), 14 deletions(-) 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 fb62cf7..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 ) ); @@ -1868,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 ) { @@ -1894,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 ) { @@ -1925,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 diff --git a/src/replxx_impl.hxx b/src/replxx_impl.hxx index 93f95a1..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 ); 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