├── replxx-config.cmake.in ├── .editorconfig ├── .gitignore ├── .travis.yml ├── examples ├── util.h ├── util.c ├── c-api.c └── cxx-api.cxx ├── .appveyor.yml ├── gen-coverage.sh ├── src ├── conversion.hxx ├── util.hxx ├── escape.hxx ├── windows.hxx ├── prompt.hxx ├── killring.hxx ├── terminal.hxx ├── utf8string.hxx ├── prompt.cxx ├── conversion.cxx ├── windows.cxx ├── history.hxx ├── util.cxx ├── ConvertUTF.h ├── unicodestring.hxx ├── ConvertUTF.cpp ├── history.cxx ├── replxx_impl.hxx ├── wcwidth.cpp └── terminal.cxx ├── .github └── workflows │ └── ci.yml ├── make.ps1 ├── LICENSE.md ├── README.md ├── CMakeLists.txt └── include ├── replxx.hxx └── replxx.h /replxx-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/replxx-targets.cmake") 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | 3 | end_of_line = lf 4 | insert_final_newline = true 5 | charset = utf-8 6 | indent_style = tab 7 | indent_size = 2 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles 3 | Makefile 4 | build 5 | cmake_install.cmake 6 | install_manifest.txt 7 | example 8 | linenoise_example 9 | libreplxx.a 10 | *.dSYM 11 | replxx_history.txt 12 | replxx_history_alt.txt 13 | *.o 14 | *~ 15 | .vs 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: C++ 2 | dist: bionic 3 | script: 4 | - ./build-all.sh 5 | - env SKIP=8bit_encoding ./tests.py 6 | addons: 7 | apt: 8 | packages: 9 | - g++ 10 | - cmake 11 | - python3-pexpect 12 | sudo: false 13 | cache: 14 | ccache: true 15 | -------------------------------------------------------------------------------- /examples/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // strrpbrk would suffice but is not portable. 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | int context_len( char const* ); 9 | int utf8str_codepoint_len( char const*, int ); 10 | 11 | #ifdef __cplusplus 12 | } 13 | #endif 14 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | branches: 3 | only: 4 | - master 5 | configuration: Release 6 | build: 7 | build_script: 8 | - md build 9 | - cd %APPVEYOR_BUILD_FOLDER%\build 10 | - cmake -G "Visual Studio 12 Win64" -DCMAKE_BUILD_TYPE=Release .. 11 | - cmake --build . --config Release 12 | -------------------------------------------------------------------------------- /gen-coverage.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | rm -rf build 4 | mkdir -p build/debug 5 | cd build/debug 6 | 7 | cmake -DCMAKE_BUILD_TYPE=coverage ../../ 8 | make 9 | 10 | cd .. 11 | 12 | lcov --base-directory .. --directory debug/CMakeFiles --capture --initial --output-file replxx-baseline.info 13 | 14 | cd .. 15 | ./tests.py 16 | cd - 17 | 18 | lcov --base-directory .. --directory debug/CMakeFiles --capture --output-file replxx-test.info 19 | lcov --add-tracefile replxx-baseline.info --add-tracefile replxx-test.info --output-file replxx-total.info 20 | lcov --extract replxx-total.info '*/replxx/src/*' '*/replxx/include/*' --output-file replxx-coverage.info 21 | genhtml replxx-coverage.info --legend --num-spaces=2 --output-directory replxx-coverage-html 22 | 23 | -------------------------------------------------------------------------------- /examples/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int utf8str_codepoint_len( char const* s, int utf8len ) { 4 | int codepointLen = 0; 5 | unsigned char m4 = 128 + 64 + 32 + 16; 6 | unsigned char m3 = 128 + 64 + 32; 7 | unsigned char m2 = 128 + 64; 8 | for ( int i = 0; i < utf8len; ++ i, ++ codepointLen ) { 9 | char c = s[i]; 10 | if ( ( c & m4 ) == m4 ) { 11 | i += 3; 12 | } else if ( ( c & m3 ) == m3 ) { 13 | i += 2; 14 | } else if ( ( c & m2 ) == m2 ) { 15 | i += 1; 16 | } 17 | } 18 | return ( codepointLen ); 19 | } 20 | 21 | int context_len( char const* prefix ) { 22 | char const wb[] = " \t\n\r\v\f-=+*&^%$#@!,./?<>;:`~'\"[]{}()\\|"; 23 | int i = (int)strlen( prefix ) - 1; 24 | int cl = 0; 25 | while ( i >= 0 ) { 26 | if ( strchr( wb, prefix[i] ) != NULL ) { 27 | break; 28 | } 29 | ++ cl; 30 | -- i; 31 | } 32 | return ( cl ); 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/conversion.hxx: -------------------------------------------------------------------------------- 1 | #ifndef REPLXX_CONVERSION_HXX_INCLUDED 2 | #define REPLXX_CONVERSION_HXX_INCLUDED 1 3 | 4 | #include "ConvertUTF.h" 5 | 6 | #ifdef __has_include 7 | #if __has_include( ) 8 | #include 9 | #endif 10 | #endif 11 | 12 | #if ! ( defined( __cpp_lib_char8_t ) || ( defined( __clang_major__ ) && ( __clang_major__ >= 8 ) && ( __cplusplus > 201703L ) ) ) 13 | namespace replxx { 14 | typedef unsigned char char8_t; 15 | } 16 | #endif 17 | 18 | namespace replxx { 19 | 20 | ConversionResult copyString8to32( char32_t* dst, int dstSize, int& dstCount, char const* src ); 21 | ConversionResult copyString8to32( char32_t* dst, int dstSize, int& dstCount, char8_t const* src ); 22 | int copyString32to8( char* dst, int dstSize, char32_t const* src, int srcSize ); 23 | 24 | namespace locale { 25 | extern bool is8BitEncoding; 26 | } 27 | 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/util.hxx: -------------------------------------------------------------------------------- 1 | #ifndef REPLXX_UTIL_HXX_INCLUDED 2 | #define REPLXX_UTIL_HXX_INCLUDED 1 3 | 4 | #include "replxx.hxx" 5 | 6 | namespace replxx { 7 | 8 | namespace color { 9 | static int unsigned const RGB666 = 16u; 10 | static int unsigned const GRAYSCALE = 232u; 11 | static int unsigned const BOLD = 1u << 17u; 12 | static int unsigned const UNDERLINE = 1u << 18u; 13 | static int unsigned const BACKGROUND_COLOR_SET = 1u << 19u; 14 | } 15 | 16 | inline bool is_control_code(char32_t testChar) { 17 | return (testChar < ' ') || // C0 controls 18 | (testChar >= 0x7F && testChar <= 0x9F); // DEL and C1 controls 19 | } 20 | 21 | inline char32_t control_to_human( char32_t key ) { 22 | return ( key < 27 ? ( key + 0x40 ) : ( key + 0x18 ) ); 23 | } 24 | 25 | int virtual_render( char32_t const*, int, int&, int&, int, int, char32_t* = nullptr, int* = nullptr ); 26 | char const* ansi_color( Replxx::Color ); 27 | std::string now_ms_str( void ); 28 | 29 | } 30 | 31 | #endif 32 | 33 | -------------------------------------------------------------------------------- /src/escape.hxx: -------------------------------------------------------------------------------- 1 | #ifndef REPLXX_ESCAPE_HXX_INCLUDED 2 | #define REPLXX_ESCAPE_HXX_INCLUDED 1 3 | 4 | namespace replxx { 5 | 6 | namespace EscapeSequenceProcessing { 7 | 8 | // This is a typedef for the routine called by doDispatch(). It takes the 9 | // current character 10 | // as input, does any required processing including reading more characters and 11 | // calling other 12 | // dispatch routines, then eventually returns the final (possibly extended or 13 | // special) character. 14 | // 15 | typedef char32_t (*CharacterDispatchRoutine)(char32_t); 16 | 17 | // This structure is used by doDispatch() to hold a list of characters to test 18 | // for and 19 | // a list of routines to call if the character matches. The dispatch routine 20 | // list is one 21 | // longer than the character list; the final entry is used if no character 22 | // matches. 23 | // 24 | struct CharacterDispatch { 25 | unsigned int len; // length of the chars list 26 | const char* chars; // chars to test 27 | CharacterDispatchRoutine* dispatch; // array of routines to call 28 | }; 29 | 30 | char32_t doDispatch(char32_t c); 31 | 32 | } 33 | 34 | } 35 | 36 | #endif 37 | 38 | -------------------------------------------------------------------------------- /src/windows.hxx: -------------------------------------------------------------------------------- 1 | #ifndef REPLXX_WINDOWS_HXX_INCLUDED 2 | #define REPLXX_WINDOWS_HXX_INCLUDED 1 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace replxx { 9 | 10 | static const int FOREGROUND_WHITE = 11 | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; 12 | static const int BACKGROUND_WHITE = 13 | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; 14 | static const int INTENSITY = FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; 15 | 16 | class WinAttributes { 17 | public: 18 | WinAttributes() { 19 | CONSOLE_SCREEN_BUFFER_INFO info; 20 | GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); 21 | _defaultAttribute = info.wAttributes & INTENSITY; 22 | _defaultColor = info.wAttributes & FOREGROUND_WHITE; 23 | _defaultBackground = info.wAttributes & BACKGROUND_WHITE; 24 | 25 | _consoleAttribute = _defaultAttribute; 26 | _consoleColor = _defaultColor | _defaultBackground; 27 | } 28 | 29 | public: 30 | int _defaultAttribute; 31 | int _defaultColor; 32 | int _defaultBackground; 33 | 34 | int _consoleAttribute; 35 | int _consoleColor; 36 | }; 37 | 38 | int win_write( HANDLE, bool, char const*, int ); 39 | 40 | extern WinAttributes WIN_ATTR; 41 | 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master ] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This workflow contains a single job called "build" 17 | build: 18 | # The type of runner that the job will run on 19 | runs-on: ubuntu-latest 20 | 21 | # Steps represent a sequence of tasks that will be executed as part of the job 22 | steps: 23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 24 | - uses: actions/checkout@v2 25 | 26 | # Install required software 27 | - name: Install dependencies 28 | run: | 29 | sudo apt-get update 30 | sudo apt-get install g++ cmake python3-pexpect 31 | 32 | # Runs a single command using the runners shell 33 | - name: Build the code 34 | run: ./build-all.sh 35 | 36 | - name: Run regression tests 37 | run: env SKIP=8bit_encoding ./tests.py 38 | 39 | -------------------------------------------------------------------------------- /src/prompt.hxx: -------------------------------------------------------------------------------- 1 | #ifndef REPLXX_PROMPT_HXX_INCLUDED 2 | #define REPLXX_PROMPT_HXX_INCLUDED 1 3 | 4 | #include 5 | 6 | #include "unicodestring.hxx" 7 | #include "terminal.hxx" 8 | 9 | namespace replxx { 10 | 11 | class Prompt { // a convenience struct for grouping prompt info 12 | public: 13 | UnicodeString _text; // our copy of the prompt text, edited 14 | int _characterCount{0}; // visible characters in _text 15 | int _extraLines{0}; // extra lines (beyond 1) occupied by prompt 16 | int _lastLinePosition{0}; // index into _text where last line begins 17 | int _cursorRowOffset{0}; // where the cursor is relative to the start of the prompt 18 | 19 | private: 20 | int _screenColumns{0}; // width of screen in columns [cache] 21 | Terminal& _terminal; 22 | public: 23 | Prompt( Terminal& ); 24 | void set_text( UnicodeString const& textPtr ); 25 | void update_state(); 26 | void update_screen_columns( void ); 27 | int screen_columns() const { 28 | return ( _screenColumns ); 29 | } 30 | void write(); 31 | int indentation() const; 32 | }; 33 | 34 | // changing prompt for "(reverse-i-search)`text':" etc. 35 | // 36 | struct DynamicPrompt : public Prompt { 37 | UnicodeString _searchText; // text we are searching for 38 | int _direction; // current search _direction, 1=forward, -1=reverse 39 | 40 | DynamicPrompt( Terminal&, int initialDirection ); 41 | void updateSearchPrompt(void); 42 | }; 43 | 44 | } 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/killring.hxx: -------------------------------------------------------------------------------- 1 | #ifndef REPLXX_KILLRING_HXX_INCLUDED 2 | #define REPLXX_KILLRING_HXX_INCLUDED 1 3 | 4 | #include 5 | 6 | #include "unicodestring.hxx" 7 | 8 | namespace replxx { 9 | 10 | class KillRing { 11 | static const int capacity = 10; 12 | int size; 13 | int index; 14 | char indexToSlot[10]; 15 | std::vector theRing; 16 | 17 | public: 18 | enum action { actionOther, actionKill, actionYank }; 19 | action lastAction; 20 | 21 | KillRing() 22 | : size(0) 23 | , index(0) 24 | , lastAction(actionOther) { 25 | theRing.reserve(capacity); 26 | } 27 | 28 | void kill(const char32_t* text, int textLen, bool forward) { 29 | if (textLen == 0) { 30 | return; 31 | } 32 | UnicodeString killedText(text, textLen); 33 | if (lastAction == actionKill && size > 0) { 34 | int slot = indexToSlot[0]; 35 | int currentLen = static_cast(theRing[slot].length()); 36 | UnicodeString temp; 37 | if ( forward ) { 38 | temp.append( theRing[slot].get(), currentLen ).append( killedText.get(), textLen ); 39 | } else { 40 | temp.append( killedText.get(), textLen ).append( theRing[slot].get(), currentLen ); 41 | } 42 | theRing[slot] = temp; 43 | } else { 44 | if (size < capacity) { 45 | if (size > 0) { 46 | memmove(&indexToSlot[1], &indexToSlot[0], size); 47 | } 48 | indexToSlot[0] = size; 49 | size++; 50 | theRing.push_back(killedText); 51 | } else { 52 | int slot = indexToSlot[capacity - 1]; 53 | theRing[slot] = killedText; 54 | memmove(&indexToSlot[1], &indexToSlot[0], capacity - 1); 55 | indexToSlot[0] = slot; 56 | } 57 | index = 0; 58 | } 59 | } 60 | 61 | UnicodeString* yank() { return (size > 0) ? &theRing[indexToSlot[index]] : 0; } 62 | 63 | UnicodeString* yankPop() { 64 | if (size == 0) { 65 | return 0; 66 | } 67 | ++index; 68 | if (index == size) { 69 | index = 0; 70 | } 71 | return &theRing[indexToSlot[index]]; 72 | } 73 | }; 74 | 75 | } 76 | 77 | #endif 78 | 79 | -------------------------------------------------------------------------------- /make.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter(Mandatory=$True)] [string]$target, 3 | [Parameter(Mandatory=$False)] [string]$prefix = "", 4 | [Parameter(Mandatory=$False)] [string]$generator = "", 5 | [Parameter(Mandatory=$False)] [switch]$with_shared = $false, 6 | [Parameter(Mandatory=$False)] [switch]$with_examples = $false 7 | ) 8 | 9 | function make_absolute( [string]$path ) { 10 | if ( -Not( [System.IO.Path]::IsPathRooted( $path ) ) ) { 11 | $path = [IO.Path]::GetFullPath( [IO.Path]::Combine( ( ($pwd).Path ), ( $path ) ) ) 12 | } 13 | return $path.Replace( "\", "/" ) 14 | } 15 | 16 | function purge { 17 | Write-Host -NoNewline "Purging... " 18 | Remove-Item "build" -Recurse -ErrorAction Ignore 19 | Write-Host "done." 20 | } 21 | 22 | function build( [string]$config, [boolean]$install, [boolean]$build_shared ) { 23 | New-Item -ItemType Directory -Force -Path "build/$config" > $null 24 | if ( $prefix -eq "" ) { 25 | throw "The ``prefix`` paremeter was not specified." 26 | } 27 | $prefix = make_absolute( $prefix ) 28 | Push-Location "build/$config" 29 | $shared="-DBUILD_SHARED_LIBS=$(if ( $build_shared ) { "ON" } else { "OFF" } )" 30 | $examples="-DREPLXX_BUILD_EXAMPLES=$( if ( $with_examples ) { "ON" } else { "OFF" } )" 31 | if ( $generator -ne "" ) { 32 | $genOpt = "-G" 33 | } 34 | cmake $shared $examples $genOpt $generator "-DCMAKE_INSTALL_PREFIX=$prefix" ../../ 35 | cmake --build . --config $config 36 | if ( $install ) { 37 | cmake --build . --target install --config $config 38 | } 39 | Pop-Location 40 | } 41 | 42 | function debug( [boolean]$install = $false ) { 43 | build "debug" $install $false 44 | if ( $with_shared ) { 45 | build "debug" $install $true 46 | } 47 | } 48 | 49 | function release( [boolean]$install = $false ) { 50 | build "release" $install $false 51 | if ( $with_shared ) { 52 | build "release" $install $true 53 | } 54 | } 55 | 56 | function install-debug { 57 | debug $true 58 | } 59 | 60 | function install-release { 61 | release $true 62 | } 63 | 64 | if ( 65 | ( $target -ne "debug" ) -and 66 | ( $target -ne "release" ) -and 67 | ( $target -ne "install-debug" ) -and 68 | ( $target -ne "install-release" ) -and 69 | ( $target -ne "purge" ) 70 | ) { 71 | Write-Error "Unknown target: ``$target``" 72 | exit 1 73 | } 74 | 75 | try { 76 | &$target 77 | } catch { 78 | Pop-Location 79 | Write-Error "$_" 80 | } 81 | -------------------------------------------------------------------------------- /src/terminal.hxx: -------------------------------------------------------------------------------- 1 | #ifndef REPLXX_IO_HXX_INCLUDED 2 | #define REPLXX_IO_HXX_INCLUDED 1 3 | 4 | #include 5 | 6 | #ifdef _WIN32 7 | #include 8 | #include 9 | #else 10 | #include 11 | #endif 12 | 13 | #include "utf8string.hxx" 14 | 15 | namespace replxx { 16 | 17 | class Terminal { 18 | public: 19 | enum class EVENT_TYPE { 20 | KEY_PRESS, 21 | MESSAGE, 22 | TIMEOUT, 23 | RESIZE 24 | }; 25 | private: 26 | #ifdef _WIN32 27 | HANDLE _consoleOut; 28 | HANDLE _consoleIn; 29 | DWORD _origOutMode; 30 | DWORD _origInMode; 31 | bool _autoEscape; 32 | WORD _oldDisplayAttribute; 33 | UINT const _inputCodePage; 34 | UINT const _outputCodePage; 35 | HANDLE _interrupt; 36 | typedef std::deque events_t; 37 | events_t _events; 38 | std::vector _empty; 39 | #else 40 | struct termios _origTermios; /* in order to restore at exit */ 41 | struct termios _rawModeTermios; /* in order to reset raw mode after callbacks */ 42 | int _interrupt[2]; 43 | #endif 44 | bool _rawMode; /* for destructor to check if restore is needed */ 45 | Utf8String _utf8; 46 | public: 47 | enum class CLEAR_SCREEN { 48 | WHOLE, 49 | TO_END 50 | }; 51 | public: 52 | Terminal( void ); 53 | ~Terminal( void ); 54 | void write32( char32_t const*, int ); 55 | void write8( char const*, int ); 56 | int get_screen_columns(void); 57 | int get_screen_rows(void); 58 | void enable_bracketed_paste( void ); 59 | void disable_bracketed_paste( void ); 60 | int enable_raw_mode(void); 61 | int reset_raw_mode(void); 62 | void disable_raw_mode(void); 63 | char32_t read_char(void); 64 | void clear_screen( CLEAR_SCREEN ); 65 | EVENT_TYPE wait_for_input( int long = 0 ); 66 | void notify_event( EVENT_TYPE ); 67 | void jump_cursor( int, int ); 68 | void set_cursor_visible( bool ); 69 | #ifndef _WIN32 70 | int read_verbatim( char32_t*, int ); 71 | int install_window_change_handler( void ); 72 | #endif 73 | private: 74 | void enable_out( void ); 75 | void disable_out( void ); 76 | private: 77 | Terminal( Terminal const& ) = delete; 78 | Terminal& operator = ( Terminal const& ) = delete; 79 | Terminal( Terminal&& ) = delete; 80 | Terminal& operator = ( Terminal&& ) = delete; 81 | }; 82 | 83 | void beep(); 84 | char32_t read_unicode_character(void); 85 | 86 | namespace tty { 87 | 88 | extern bool in; 89 | extern bool out; 90 | 91 | } 92 | 93 | } 94 | 95 | #endif 96 | 97 | -------------------------------------------------------------------------------- /src/utf8string.hxx: -------------------------------------------------------------------------------- 1 | #ifndef REPLXX_UTF8STRING_HXX_INCLUDED 2 | #define REPLXX_UTF8STRING_HXX_INCLUDED 3 | 4 | #include 5 | 6 | #include "unicodestring.hxx" 7 | 8 | namespace replxx { 9 | 10 | class Utf8String { 11 | private: 12 | typedef std::unique_ptr buffer_t; 13 | buffer_t _data; 14 | int _bufSize; 15 | int _len; 16 | public: 17 | Utf8String( void ) 18 | : _data() 19 | , _bufSize( 0 ) 20 | , _len( 0 ) { 21 | } 22 | explicit Utf8String( UnicodeString const& src ) 23 | : _data() 24 | , _bufSize( 0 ) 25 | , _len( 0 ) { 26 | assign( src, src.length() ); 27 | } 28 | 29 | Utf8String( UnicodeString const& src_, int len_ ) 30 | : _data() 31 | , _bufSize( 0 ) 32 | , _len( 0 ) { 33 | assign( src_, len_ ); 34 | } 35 | 36 | void assign( UnicodeString const& str_ ) { 37 | assign( str_, str_.length() ); 38 | } 39 | 40 | void assign( UnicodeString const& str_, int len_ ) { 41 | assign( str_.get(), len_ ); 42 | } 43 | 44 | void assign( char32_t const* str_, int len_ ) { 45 | int len( len_ * 4 ); 46 | realloc( len ); 47 | _len = copyString32to8( _data.get(), len, str_, len_ ); 48 | } 49 | 50 | void assign( std::string const& str_ ) { 51 | realloc( static_cast( str_.length() ) ); 52 | strncpy( _data.get(), str_.c_str(), str_.length() ); 53 | _len = static_cast( str_.length() ); 54 | } 55 | 56 | void assign( Utf8String const& other_ ) { 57 | realloc( other_._len ); 58 | strncpy( _data.get(), other_._data.get(), other_._len ); 59 | _len = other_._len; 60 | } 61 | 62 | char const* get() const { 63 | return _data.get(); 64 | } 65 | 66 | int size( void ) const { 67 | return ( _len ); 68 | } 69 | 70 | bool operator != ( Utf8String const& other_ ) { 71 | return ( 72 | ( other_._len != _len ) 73 | || ( 74 | ( _len != 0 ) 75 | && ( memcmp( other_._data.get(), _data.get(), _len ) != 0 ) 76 | ) 77 | ); 78 | } 79 | 80 | private: 81 | void realloc( int reqLen ) { 82 | if ( ( reqLen + 1 ) > _bufSize ) { 83 | _bufSize = 1; 84 | while ( ( reqLen + 1 ) > _bufSize ) { 85 | _bufSize *= 2; 86 | } 87 | _data.reset( new char[_bufSize] ); 88 | memset( _data.get(), 0, _bufSize ); 89 | } 90 | _data[reqLen] = 0; 91 | return; 92 | } 93 | Utf8String(const Utf8String&) = delete; 94 | Utf8String& operator=(const Utf8String&) = delete; 95 | }; 96 | 97 | } 98 | 99 | #endif 100 | 101 | -------------------------------------------------------------------------------- /src/prompt.cxx: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | 3 | #include 4 | #include 5 | #include 6 | #if _MSC_VER < 1900 && defined (_MSC_VER) 7 | #define snprintf _snprintf // Microsoft headers use underscores in some names 8 | #endif 9 | #define strcasecmp _stricmp 10 | #define strdup _strdup 11 | #define write _write 12 | #define STDIN_FILENO 0 13 | 14 | #else /* _WIN32 */ 15 | 16 | #include 17 | 18 | #endif /* _WIN32 */ 19 | 20 | #include "prompt.hxx" 21 | #include "util.hxx" 22 | 23 | namespace replxx { 24 | 25 | Prompt::Prompt( Terminal& terminal_ ) 26 | : _extraLines( 0 ) 27 | , _lastLinePosition( 0 ) 28 | , _cursorRowOffset( 0 ) 29 | , _screenColumns( 0 ) 30 | , _terminal( terminal_ ) { 31 | } 32 | 33 | void Prompt::write() { 34 | _terminal.write32( _text.get(), _text.length() ); 35 | } 36 | 37 | void Prompt::update_screen_columns( void ) { 38 | _screenColumns = _terminal.get_screen_columns(); 39 | } 40 | 41 | void Prompt::set_text( UnicodeString const& text_ ) { 42 | _text = text_; 43 | update_state(); 44 | } 45 | 46 | void Prompt::update_state() { 47 | _cursorRowOffset -= _extraLines; 48 | _extraLines = 0; 49 | _lastLinePosition = 0; 50 | _screenColumns = 0; 51 | update_screen_columns(); 52 | // strip control characters from the prompt -- we do allow newline 53 | UnicodeString::const_iterator in( _text.begin() ); 54 | 55 | int x = 0; 56 | int renderedSize( 0 ); 57 | _characterCount = virtual_render( _text.get(), _text.length(), x, _extraLines, _screenColumns, 0, _text.get(), &renderedSize ); 58 | _lastLinePosition = _characterCount - x; 59 | _text.erase( renderedSize, _text.length() - renderedSize ); 60 | 61 | _cursorRowOffset += _extraLines; 62 | } 63 | 64 | int Prompt::indentation() const { 65 | return _characterCount - _lastLinePosition; 66 | } 67 | 68 | // Used with DynamicPrompt (history search) 69 | // 70 | const UnicodeString forwardSearchBasePrompt("(i-search)`"); 71 | const UnicodeString reverseSearchBasePrompt("(reverse-i-search)`"); 72 | const UnicodeString endSearchBasePrompt("': "); 73 | 74 | DynamicPrompt::DynamicPrompt( Terminal& terminal_, int initialDirection ) 75 | : Prompt( terminal_ ) 76 | , _searchText() 77 | , _direction( initialDirection ) { 78 | updateSearchPrompt(); 79 | } 80 | 81 | void DynamicPrompt::updateSearchPrompt(void) { 82 | update_screen_columns(); 83 | const UnicodeString* basePrompt = 84 | (_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt; 85 | _text.assign( *basePrompt ).append( _searchText ).append( endSearchBasePrompt ); 86 | update_state(); 87 | } 88 | 89 | } 90 | 91 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org) 2 | Copyright (c) 2010, Salvatore Sanfilippo (antirez at gmail dot com) 3 | Copyright (c) 2010, Pieter Noordhuis (pcnoordhuis at gmail dot com) 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | * Neither the name of Redis nor the names of its contributors may be used 16 | to endorse or promote products derived from this software without 17 | specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | wcwidth.cpp 33 | =========== 34 | 35 | Markus Kuhn -- 2007-05-26 (Unicode 5.0) 36 | 37 | Permission to use, copy, modify, and distribute this software 38 | for any purpose and without fee is hereby granted. The author 39 | disclaims all warranties with regard to this software. 40 | 41 | 42 | ConvertUTF.cpp 43 | ============== 44 | 45 | Copyright 2001-2004 Unicode, Inc. 46 | 47 | Disclaimer 48 | 49 | This source code is provided as is by Unicode, Inc. No claims are 50 | made as to fitness for any particular purpose. No warranties of any 51 | kind are expressed or implied. The recipient agrees to determine 52 | applicability of information provided. If this file has been 53 | purchased on magnetic or optical media from Unicode, Inc., the 54 | sole remedy for any claim will be exchange of defective media 55 | within 90 days of receipt. 56 | 57 | Limitations on Rights to Redistribute This Code 58 | 59 | Unicode, Inc. hereby grants the right to freely use the information 60 | supplied in this file in the creation of products supporting the 61 | Unicode Standard, and to make copies of this file in any form 62 | for internal or external distribution as long as this notice 63 | remains attached. 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Read Evaluate Print Loop ++ 2 | 3 | ![demo](https://codestation.org/download/replxx.gif) 4 | 5 | ![Build Status](https://github.com/AmokHuginnsson/replxx/actions/workflows/ci.yml/badge.svg) 6 | 7 | A small, portable GNU readline replacement for Linux, Windows and 8 | MacOS which is capable of handling UTF-8 characters. Unlike GNU 9 | readline, which is GPL, this library uses a BSD license and can be 10 | used in any kind of program. 11 | 12 | ## Origin 13 | 14 | This replxx implementation is based on the work by 15 | [ArangoDB Team](https://github.com/arangodb/linenoise-ng) and 16 | [Salvatore Sanfilippo](https://github.com/antirez/linenoise) and 17 | 10gen Inc. The goal is to create a zero-config, BSD 18 | licensed, readline replacement usable in Apache2 or BSD licensed 19 | programs. 20 | 21 | ## Features 22 | 23 | * single-line and multi-line editing mode with the usual key bindings implemented 24 | * history handling 25 | * completion 26 | * syntax highlighting 27 | * hints 28 | * BSD license source code 29 | * Only uses a subset of VT100 escapes (ANSI.SYS compatible) 30 | * UTF8 aware 31 | * support for Linux, MacOS and Windows 32 | 33 | ## Requirements 34 | 35 | To build this library, you will need a C++11-enabled compiler and 36 | some recent version of CMake. 37 | 38 | ## Build instructions 39 | 40 | ### *nix 41 | 42 | 1. Create a build directory 43 | 44 | ```bash 45 | mkdir -p build && cd build 46 | ``` 47 | 48 | 2. Build the library 49 | 50 | ```bash 51 | cmake -DCMAKE_BUILD_TYPE=Release .. && make 52 | ``` 53 | 54 | 3. Install the library at the default target location 55 | 56 | ```bash 57 | sudo make install 58 | ``` 59 | 60 | The default installation location can be adjusted by setting the `DESTDIR` 61 | variable when invoking `make install`: 62 | 63 | ```bash 64 | make DESTDIR=/tmp install 65 | ``` 66 | 67 | ### Windows 68 | 69 | 1. Create a build directory in MS-DOS command prompt 70 | 71 | ``` 72 | md build 73 | cd build 74 | ``` 75 | 76 | 2. Generate Visual Studio solution file with cmake 77 | 78 | * 32 bit: 79 | ```bash 80 | cmake -G "Visual Studio 12 2013" -DCMAKE_BUILD_TYPE=Release .. 81 | ``` 82 | * 64 bit: 83 | ```bash 84 | cmake -G "Visual Studio 12 2013 Win64" -DCMAKE_BUILD_TYPE=Release .. 85 | ``` 86 | 87 | 3. Open the generated file `replxx.sln` in the `build` subdirectory with Visual Studio. 88 | 89 | ## Tested with... 90 | 91 | * Linux text only console ($TERM = linux) 92 | * Linux KDE terminal application ($TERM = xterm) 93 | * Linux xterm ($TERM = xterm) 94 | * Linux Buildroot ($TERM = vt100) 95 | * Mac OS X iTerm ($TERM = xterm) 96 | * Mac OS X default Terminal.app ($TERM = xterm) 97 | * OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen) 98 | * IBM AIX 6.1 99 | * FreeBSD xterm ($TERM = xterm) 100 | * ANSI.SYS 101 | * Emacs comint mode ($TERM = dumb) 102 | * Windows 103 | 104 | Please test it everywhere you can and report back! 105 | 106 | -------------------------------------------------------------------------------- /src/conversion.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "conversion.hxx" 8 | 9 | #ifdef _WIN32 10 | #define strdup _strdup 11 | #endif 12 | 13 | using namespace std; 14 | 15 | namespace replxx { 16 | 17 | namespace locale { 18 | 19 | void to_lower( std::string& s_ ) { 20 | transform( s_.begin(), s_.end(), s_.begin(), static_cast( &tolower ) ); 21 | } 22 | 23 | bool is_8bit_encoding( void ) { 24 | bool is8BitEncoding( false ); 25 | string origLC( setlocale( LC_CTYPE, nullptr ) ); 26 | string lc( origLC ); 27 | to_lower( lc ); 28 | if ( lc == "c" ) { 29 | setlocale( LC_CTYPE, "" ); 30 | } 31 | lc = setlocale( LC_CTYPE, nullptr ); 32 | setlocale( LC_CTYPE, origLC.c_str() ); 33 | to_lower( lc ); 34 | if ( lc.find( "8859" ) != std::string::npos ) { 35 | is8BitEncoding = true; 36 | } 37 | return ( is8BitEncoding ); 38 | } 39 | 40 | bool is8BitEncoding( is_8bit_encoding() ); 41 | 42 | } 43 | 44 | ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char* src) { 45 | ConversionResult res = ConversionResult::conversionOK; 46 | if ( ! locale::is8BitEncoding ) { 47 | const UTF8* sourceStart = reinterpret_cast(src); 48 | const UTF8* sourceEnd = sourceStart + strlen(src); 49 | UTF32* targetStart = reinterpret_cast(dst); 50 | UTF32* targetEnd = targetStart + dstSize; 51 | 52 | res = ConvertUTF8toUTF32( 53 | &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion); 54 | 55 | if (res == conversionOK) { 56 | dstCount = static_cast( targetStart - reinterpret_cast( dst ) ); 57 | 58 | if (dstCount < dstSize) { 59 | *targetStart = 0; 60 | } 61 | } 62 | } else { 63 | for ( dstCount = 0; ( dstCount < dstSize ) && src[dstCount]; ++ dstCount ) { 64 | dst[dstCount] = src[dstCount]; 65 | } 66 | } 67 | return res; 68 | } 69 | 70 | ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char8_t* src) { 71 | return copyString8to32( 72 | dst, dstSize, dstCount, reinterpret_cast(src) 73 | ); 74 | } 75 | 76 | int copyString32to8( char* dst, int dstSize, const char32_t* src, int srcSize ) { 77 | int resCount( 0 ); 78 | if ( ! locale::is8BitEncoding ) { 79 | const UTF32* sourceStart = reinterpret_cast(src); 80 | const UTF32* sourceEnd = sourceStart + srcSize; 81 | UTF8* targetStart = reinterpret_cast(dst); 82 | UTF8* targetEnd = targetStart + dstSize; 83 | 84 | ConversionResult res = ConvertUTF32toUTF8( 85 | &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion 86 | ); 87 | 88 | if ( res == conversionOK ) { 89 | resCount = static_cast( targetStart - reinterpret_cast( dst ) ); 90 | if ( resCount < dstSize ) { 91 | *targetStart = 0; 92 | } 93 | } 94 | } else { 95 | int i( 0 ); 96 | for ( i = 0; ( i < dstSize ) && ( i < srcSize ) && src[i]; ++ i ) { 97 | dst[i] = static_cast( src[i] ); 98 | } 99 | resCount = i; 100 | if ( i < dstSize ) { 101 | dst[i] = 0; 102 | } 103 | } 104 | return ( resCount ); 105 | } 106 | 107 | } 108 | 109 | -------------------------------------------------------------------------------- /src/windows.cxx: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | 3 | #include 4 | 5 | #include "windows.hxx" 6 | #include "conversion.hxx" 7 | #include "terminal.hxx" 8 | 9 | using namespace std; 10 | 11 | namespace replxx { 12 | 13 | WinAttributes WIN_ATTR; 14 | 15 | template 16 | T* HandleEsc(HANDLE out_, T* p, T* end) { 17 | if (*p == '[') { 18 | int code = 0; 19 | 20 | int thisBackground( WIN_ATTR._defaultBackground ); 21 | for (++p; p < end; ++p) { 22 | char32_t c = *p; 23 | 24 | if ('0' <= c && c <= '9') { 25 | code = code * 10 + (c - '0'); 26 | } else if (c == 'm' || c == ';') { 27 | switch (code) { 28 | case 0: 29 | WIN_ATTR._consoleAttribute = WIN_ATTR._defaultAttribute; 30 | WIN_ATTR._consoleColor = WIN_ATTR._defaultColor | thisBackground; 31 | break; 32 | case 1: // BOLD 33 | case 5: // BLINK 34 | WIN_ATTR._consoleAttribute = (WIN_ATTR._defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY; 35 | break; 36 | case 22: 37 | WIN_ATTR._consoleAttribute = WIN_ATTR._defaultAttribute; 38 | break; 39 | case 30: 40 | case 90: 41 | WIN_ATTR._consoleColor = thisBackground; 42 | break; 43 | case 31: 44 | case 91: 45 | WIN_ATTR._consoleColor = FOREGROUND_RED | thisBackground; 46 | break; 47 | case 32: 48 | case 92: 49 | WIN_ATTR._consoleColor = FOREGROUND_GREEN | thisBackground; 50 | break; 51 | case 33: 52 | case 93: 53 | WIN_ATTR._consoleColor = FOREGROUND_RED | FOREGROUND_GREEN | thisBackground; 54 | break; 55 | case 34: 56 | case 94: 57 | WIN_ATTR._consoleColor = FOREGROUND_BLUE | thisBackground; 58 | break; 59 | case 35: 60 | case 95: 61 | WIN_ATTR._consoleColor = FOREGROUND_BLUE | FOREGROUND_RED | thisBackground; 62 | break; 63 | case 36: 64 | case 96: 65 | WIN_ATTR._consoleColor = FOREGROUND_BLUE | FOREGROUND_GREEN | thisBackground; 66 | break; 67 | case 37: 68 | case 97: 69 | WIN_ATTR._consoleColor = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | thisBackground; 70 | break; 71 | case 101: 72 | thisBackground = BACKGROUND_RED; 73 | break; 74 | } 75 | 76 | if ( ( code >= 90 ) && ( code <= 97 ) ) { 77 | WIN_ATTR._consoleAttribute = (WIN_ATTR._defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY; 78 | } 79 | 80 | code = 0; 81 | } 82 | 83 | if (*p == 'm') { 84 | ++p; 85 | break; 86 | } 87 | } 88 | } else { 89 | ++p; 90 | } 91 | 92 | SetConsoleTextAttribute( 93 | out_, 94 | WIN_ATTR._consoleAttribute | WIN_ATTR._consoleColor 95 | ); 96 | 97 | return p; 98 | } 99 | 100 | int win_write( HANDLE out_, bool autoEscape_, char const* str_, int size_ ) { 101 | int count( 0 ); 102 | if ( tty::out ) { 103 | DWORD nWritten( 0 ); 104 | if ( autoEscape_ ) { 105 | WriteConsoleA( out_, str_, size_, &nWritten, nullptr ); 106 | count = nWritten; 107 | } else { 108 | char const* s( str_ ); 109 | char const* e( str_ + size_ ); 110 | while ( str_ < e ) { 111 | if ( *str_ == 27 ) { 112 | if ( s < str_ ) { 113 | int toWrite( static_cast( str_ - s ) ); 114 | WriteConsoleA( out_, s, static_cast( toWrite ), &nWritten, nullptr ); 115 | count += nWritten; 116 | if ( static_cast( nWritten ) != toWrite ) { 117 | s = str_ = nullptr; 118 | break; 119 | } 120 | } 121 | s = HandleEsc( out_, str_ + 1, e ); 122 | int escaped( static_cast( s - str_ ) ); 123 | count += escaped; 124 | str_ = s; 125 | } else { 126 | ++ str_; 127 | } 128 | } 129 | 130 | if ( s < str_ ) { 131 | WriteConsoleA( out_, s, static_cast( str_ - s ), &nWritten, nullptr ); 132 | count += nWritten; 133 | } 134 | } 135 | } else { 136 | count = _write( 1, str_, size_ ); 137 | } 138 | return ( count ); 139 | } 140 | 141 | } 142 | 143 | #endif 144 | 145 | -------------------------------------------------------------------------------- /src/history.hxx: -------------------------------------------------------------------------------- 1 | #ifndef REPLXX_HISTORY_HXX_INCLUDED 2 | #define REPLXX_HISTORY_HXX_INCLUDED 1 3 | 4 | #include 5 | #include 6 | 7 | #include "unicodestring.hxx" 8 | #include "utf8string.hxx" 9 | #include "conversion.hxx" 10 | #include "util.hxx" 11 | 12 | namespace std { 13 | template<> 14 | struct hash { 15 | std::size_t operator()( replxx::UnicodeString const& us_ ) const { 16 | std::size_t h( 0 ); 17 | char32_t const* p( us_.get() ); 18 | char32_t const* e( p + us_.length() ); 19 | while ( p != e ) { 20 | h *= 31; 21 | h += *p; 22 | ++ p; 23 | } 24 | return ( h ); 25 | } 26 | }; 27 | } 28 | 29 | namespace replxx { 30 | 31 | class History { 32 | public: 33 | class Entry { 34 | std::string _timestamp; 35 | UnicodeString _text; 36 | UnicodeString _scratch; 37 | public: 38 | Entry( std::string const& timestamp_, UnicodeString const& text_ ) 39 | : _timestamp( timestamp_ ) 40 | , _text( text_ ) 41 | , _scratch( text_ ) { 42 | } 43 | std::string const& timestamp( void ) const { 44 | return ( _timestamp ); 45 | } 46 | UnicodeString const& text( void ) const { 47 | return ( _scratch ); 48 | } 49 | void set_scratch( UnicodeString const& s ) { 50 | _scratch = s; 51 | } 52 | void reset_scratch( void ) { 53 | _scratch = _text; 54 | } 55 | bool operator < ( Entry const& other_ ) const { 56 | return ( _timestamp < other_._timestamp ); 57 | } 58 | }; 59 | typedef std::list entries_t; 60 | typedef std::unordered_map locations_t; 61 | private: 62 | entries_t _entries; 63 | locations_t _locations; 64 | int _maxSize; 65 | entries_t::iterator _current; 66 | entries_t::const_iterator _yankPos; 67 | /* 68 | * _previous and _recallMostRecent are used to allow 69 | * HISTORY_NEXT action (a down-arrow key) to have a special meaning 70 | * if invoked after a line from history was accepted without 71 | * any modification. 72 | * Special meaning is: a down arrow shall jump to the line one 73 | * after previously accepted from history. 74 | */ 75 | entries_t::iterator _previous; 76 | bool _recallMostRecent; 77 | bool _unique; 78 | public: 79 | History( void ); 80 | void add( UnicodeString const& line, std::string const& when = now_ms_str() ); 81 | bool save( std::string const& filename, bool ); 82 | void save( std::ostream& histFile ); 83 | bool load( std::string const& filename ); 84 | void load( std::istream& histFile ); 85 | void clear( void ); 86 | void set_max_size( int len ); 87 | void set_unique( bool unique_ ) { 88 | _unique = unique_; 89 | remove_duplicates(); 90 | } 91 | void reset_yank_iterator(); 92 | bool next_yank_position( void ); 93 | void reset_recall_most_recent( void ) { 94 | _recallMostRecent = false; 95 | } 96 | void commit_index( void ) { 97 | _previous = _current; 98 | _recallMostRecent = true; 99 | } 100 | bool is_empty( void ) const { 101 | return ( _entries.empty() ); 102 | } 103 | void update_last( UnicodeString const& ); 104 | void drop_last( void ); 105 | bool is_last( void ); 106 | bool move( bool ); 107 | void set_current_scratch( UnicodeString const& s ) { 108 | _current->set_scratch( s ); 109 | } 110 | void reset_scratches( void ) { 111 | for ( Entry& entry : _entries ) { 112 | entry.reset_scratch(); 113 | } 114 | } 115 | void reset_current_scratch( void ) { 116 | _current->reset_scratch(); 117 | } 118 | UnicodeString const& current( void ) const { 119 | return ( _current->text() ); 120 | } 121 | UnicodeString const& yank_line( void ) const { 122 | return ( _yankPos->text() ); 123 | } 124 | void jump( bool, bool = true ); 125 | bool common_prefix_search( UnicodeString const&, int, bool, bool ); 126 | int size( void ) const { 127 | return ( static_cast( _entries.size() ) ); 128 | } 129 | Replxx::HistoryScan::impl_t scan( void ) const; 130 | void save_pos( void ); 131 | void restore_pos( void ); 132 | private: 133 | History( History const& ) = delete; 134 | History& operator = ( History const& ) = delete; 135 | bool move( entries_t::iterator&, int, bool = false ); 136 | entries_t::iterator moved( entries_t::iterator, int, bool = false ); 137 | void erase( entries_t::iterator ); 138 | void trim_to_max_size( void ); 139 | void remove_duplicate( UnicodeString const& ); 140 | void remove_duplicates( void ); 141 | void do_load( std::istream& ); 142 | entries_t::iterator last( void ); 143 | void sort( void ); 144 | void reset_iters( void ); 145 | }; 146 | 147 | class Replxx::HistoryScanImpl { 148 | History::entries_t const& _entries; 149 | History::entries_t::const_iterator _it; 150 | mutable Utf8String _utf8Cache; 151 | mutable Replxx::HistoryEntry _entryCache; 152 | mutable bool _cacheValid; 153 | public: 154 | HistoryScanImpl( History::entries_t const& ); 155 | bool next( void ); 156 | Replxx::HistoryEntry const& get( void ) const; 157 | }; 158 | 159 | } 160 | 161 | #endif 162 | 163 | -------------------------------------------------------------------------------- /src/util.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "util.hxx" 8 | #include "terminal.hxx" 9 | 10 | #undef min 11 | 12 | namespace replxx { 13 | 14 | int mk_wcwidth( char32_t ); 15 | 16 | int virtual_render( char32_t const* display_, int size_, int& x_, int& y_, int screenColumns_, int promptLen_, char32_t* rendered_, int* renderedSize_ ) { 17 | char32_t* out( rendered_ ); 18 | int visibleCount( 0 ); 19 | auto render = [&rendered_, &renderedSize_, &out, &visibleCount]( char32_t c_, bool visible_, bool renderAttributes_ = true ) { 20 | if ( rendered_ && renderedSize_ && renderAttributes_ ) { 21 | *out = c_; 22 | ++ out; 23 | if ( visible_ ) { 24 | ++ visibleCount; 25 | } 26 | } 27 | }; 28 | bool wrapped( false ); 29 | auto advance_cursor = [&x_, &y_, &screenColumns_, &wrapped]( int by_ = 1 ) { 30 | wrapped = false; 31 | x_ += by_; 32 | if ( x_ >= screenColumns_ ) { 33 | x_ = 0; 34 | ++ y_; 35 | wrapped = true; 36 | } 37 | }; 38 | bool const renderAttributes( !!tty::out ); 39 | int pos( 0 ); 40 | while ( pos < size_ ) { 41 | char32_t c( display_[pos] ); 42 | if ( ( c == '\n' ) || ( c == '\r' ) ) { 43 | render( c, true ); 44 | if ( ( c == '\n' ) && ! wrapped ) { 45 | ++ y_; 46 | } 47 | x_ = promptLen_; 48 | ++ pos; 49 | continue; 50 | } 51 | if ( c == '\b' ) { 52 | render( c, true ); 53 | -- x_; 54 | if ( x_ < 0 ) { 55 | x_ = screenColumns_ - 1; 56 | -- y_; 57 | } 58 | ++ pos; 59 | continue; 60 | } 61 | if ( c == '\033' ) { 62 | render( c, false, renderAttributes ); 63 | ++ pos; 64 | if ( pos >= size_ ) { 65 | advance_cursor( 2 ); 66 | continue; 67 | } 68 | c = display_[pos]; 69 | if ( c != '[' ) { 70 | advance_cursor( 2 ); 71 | continue; 72 | } 73 | render( c, false, renderAttributes ); 74 | ++ pos; 75 | if ( pos >= size_ ) { 76 | advance_cursor( 3 ); 77 | continue; 78 | } 79 | int codeLen( 0 ); 80 | while ( pos < size_ ) { 81 | c = display_[pos]; 82 | if ( ( c != ';' ) && ( ( c < '0' ) || ( c > '9' ) ) ) { 83 | break; 84 | } 85 | render( c, false, renderAttributes ); 86 | ++ codeLen; 87 | ++ pos; 88 | } 89 | if ( pos >= size_ ) { 90 | continue; 91 | } 92 | c = display_[pos]; 93 | if ( c != 'm' ) { 94 | advance_cursor( 3 + codeLen ); 95 | continue; 96 | } 97 | render( c, false, renderAttributes ); 98 | ++ pos; 99 | continue; 100 | } 101 | if ( is_control_code( c ) ) { 102 | render( c, true ); 103 | advance_cursor( 2 ); 104 | ++ pos; 105 | continue; 106 | } 107 | int wcw( mk_wcwidth( c ) ); 108 | if ( wcw < 0 ) { 109 | break; 110 | } 111 | render( c, true ); 112 | advance_cursor( wcw ); 113 | ++ pos; 114 | } 115 | if ( rendered_ && renderedSize_ ) { 116 | *renderedSize_ = out - rendered_; 117 | } 118 | return ( visibleCount ); 119 | } 120 | 121 | char const* ansi_color( Replxx::Color color_ ) { 122 | int unsigned code( static_cast( color_ ) ); 123 | int unsigned fg( code & 0xFFu ); 124 | int unsigned bg( ( code >> 8 ) & 0xFFu ); 125 | char const* bold( ( code & color::BOLD ) != 0 ? ";1" : "" ); 126 | char const* underline = ( ( code & color::UNDERLINE ) != 0 ? ";4" : "" ); 127 | static int const MAX_COLOR_CODE_SIZE( 32 ); 128 | static char colorBuffer[MAX_COLOR_CODE_SIZE]; 129 | int pos( 0 ); 130 | if ( ( code & static_cast( Replxx::Color::DEFAULT ) ) != 0 ) { 131 | pos = snprintf( colorBuffer, MAX_COLOR_CODE_SIZE, "\033[0%s%sm", underline, bold ); 132 | } else if ( fg <= static_cast( Replxx::Color::LIGHTGRAY ) ) { 133 | pos = snprintf( colorBuffer, MAX_COLOR_CODE_SIZE, "\033[0;22;3%d%s%sm", fg, underline, bold ); 134 | } else if ( fg <= static_cast( Replxx::Color::WHITE ) ) { 135 | #ifdef _WIN32 136 | static bool const has256colorDefault( true ); 137 | #else 138 | static bool const has256colorDefault( false ); 139 | #endif 140 | static char const* TERM( getenv( "TERM" ) ); 141 | static bool const has256color( TERM ? ( strstr( TERM, "256" ) != nullptr ) : has256colorDefault ); 142 | static char const* ansiEscapeCodeTemplate = has256color ? "\033[0;9%d%s%sm" : "\033[0;1;3%d%s%sm"; 143 | pos = snprintf( colorBuffer, MAX_COLOR_CODE_SIZE, ansiEscapeCodeTemplate, fg - static_cast( Replxx::Color::GRAY ), underline, bold ); 144 | } else { 145 | pos = snprintf( colorBuffer, MAX_COLOR_CODE_SIZE, "\033[0;38;5;%d%s%sm", fg, underline, bold ); 146 | } 147 | if ( ( code & color::BACKGROUND_COLOR_SET ) == 0 ) { 148 | return colorBuffer; 149 | } 150 | if ( bg <= static_cast( Replxx::Color::WHITE ) ) { 151 | if ( bg <= static_cast( Replxx::Color::LIGHTGRAY ) ) { 152 | snprintf( colorBuffer + pos, MAX_COLOR_CODE_SIZE - pos, "\033[4%dm", bg ); 153 | } else { 154 | snprintf( colorBuffer + pos, MAX_COLOR_CODE_SIZE - pos, "\033[10%dm", bg - static_cast( Replxx::Color::GRAY ) ); 155 | } 156 | } else { 157 | snprintf( colorBuffer + pos, MAX_COLOR_CODE_SIZE - pos, "\033[48;5;%dm", bg ); 158 | } 159 | return colorBuffer; 160 | } 161 | 162 | std::string now_ms_str( void ) { 163 | std::chrono::milliseconds ms( std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ) ); 164 | time_t t( ms.count() / 1000 ); 165 | tm broken; 166 | #ifdef _WIN32 167 | #define localtime_r( t, b ) localtime_s( ( b ), ( t ) ) 168 | #endif 169 | localtime_r( &t, &broken ); 170 | #undef localtime_r 171 | static int const BUFF_SIZE( 32 ); 172 | char str[BUFF_SIZE]; 173 | strftime( str, BUFF_SIZE, "%Y-%m-%d %H:%M:%S.", &broken ); 174 | snprintf( str + sizeof ( "YYYY-mm-dd HH:MM:SS" ), 5, "%03d", static_cast( ms.count() % 1000 ) ); 175 | return ( str ); 176 | } 177 | 178 | } 179 | 180 | -------------------------------------------------------------------------------- /src/ConvertUTF.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2001-2004 Unicode, Inc. 3 | * 4 | * Disclaimer 5 | * 6 | * This source code is provided as is by Unicode, Inc. No claims are 7 | * made as to fitness for any particular purpose. No warranties of any 8 | * kind are expressed or implied. The recipient agrees to determine 9 | * applicability of information provided. If this file has been 10 | * purchased on magnetic or optical media from Unicode, Inc., the 11 | * sole remedy for any claim will be exchange of defective media 12 | * within 90 days of receipt. 13 | * 14 | * Limitations on Rights to Redistribute This Code 15 | * 16 | * Unicode, Inc. hereby grants the right to freely use the information 17 | * supplied in this file in the creation of products supporting the 18 | * Unicode Standard, and to make copies of this file in any form 19 | * for internal or external distribution as long as this notice 20 | * remains attached. 21 | */ 22 | 23 | /* --------------------------------------------------------------------- 24 | 25 | Conversions between UTF32, UTF-16, and UTF-8. Header file. 26 | 27 | Several funtions are included here, forming a complete set of 28 | conversions between the three formats. UTF-7 is not included 29 | here, but is handled in a separate source file. 30 | 31 | Each of these routines takes pointers to input buffers and output 32 | buffers. The input buffers are const. 33 | 34 | Each routine converts the text between *sourceStart and sourceEnd, 35 | putting the result into the buffer between *targetStart and 36 | targetEnd. Note: the end pointers are *after* the last item: e.g. 37 | *(sourceEnd - 1) is the last item. 38 | 39 | The return result indicates whether the conversion was successful, 40 | and if not, whether the problem was in the source or target buffers. 41 | (Only the first encountered problem is indicated.) 42 | 43 | After the conversion, *sourceStart and *targetStart are both 44 | updated to point to the end of last text successfully converted in 45 | the respective buffers. 46 | 47 | Input parameters: 48 | sourceStart - pointer to a pointer to the source buffer. 49 | The contents of this are modified on return so that 50 | it points at the next thing to be converted. 51 | targetStart - similarly, pointer to pointer to the target buffer. 52 | sourceEnd, targetEnd - respectively pointers to the ends of the 53 | two buffers, for overflow checking only. 54 | 55 | These conversion functions take a ConversionFlags argument. When this 56 | flag is set to strict, both irregular sequences and isolated surrogates 57 | will cause an error. When the flag is set to lenient, both irregular 58 | sequences and isolated surrogates are converted. 59 | 60 | Whether the flag is strict or lenient, all illegal sequences will cause 61 | an error return. This includes sequences such as: , , 62 | or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code 63 | must check for illegal sequences. 64 | 65 | When the flag is set to lenient, characters over 0x10FFFF are converted 66 | to the replacement character; otherwise (when the flag is set to strict) 67 | they constitute an error. 68 | 69 | Output parameters: 70 | The value "sourceIllegal" is returned from some routines if the input 71 | sequence is malformed. When "sourceIllegal" is returned, the source 72 | value will point to the illegal value that caused the problem. E.g., 73 | in UTF-8 when a sequence is malformed, it points to the start of the 74 | malformed sequence. 75 | 76 | Author: Mark E. Davis, 1994. 77 | Rev History: Rick McGowan, fixes & updates May 2001. 78 | Fixes & updates, Sept 2001. 79 | 80 | ------------------------------------------------------------------------ */ 81 | 82 | /* --------------------------------------------------------------------- 83 | The following 4 definitions are compiler-specific. 84 | The C standard does not guarantee that wchar_t has at least 85 | 16 bits, so wchar_t is no less portable than unsigned short! 86 | All should be unsigned values to avoid sign extension during 87 | bit mask & shift operations. 88 | ------------------------------------------------------------------------ */ 89 | 90 | #ifndef REPLXX_CONVERT_UTF8_H_INCLUDED 91 | #define REPLXX_CONVERT_UTF8_H_INCLUDED 1 92 | 93 | #if 0 94 | typedef unsigned long UTF32; /* at least 32 bits */ 95 | typedef unsigned short UTF16; /* at least 16 bits */ 96 | typedef unsigned char UTF8; /* typically 8 bits */ 97 | #endif 98 | 99 | #include 100 | #include 101 | 102 | namespace replxx { 103 | 104 | typedef uint32_t UTF32; 105 | typedef uint16_t UTF16; 106 | typedef uint8_t UTF8; 107 | 108 | /* Some fundamental constants */ 109 | #define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD 110 | #define UNI_MAX_BMP (UTF32)0x0000FFFF 111 | #define UNI_MAX_UTF16 (UTF32)0x0010FFFF 112 | #define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF 113 | #define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF 114 | 115 | typedef enum { 116 | conversionOK, /* conversion successful */ 117 | sourceExhausted, /* partial character in source, but hit end */ 118 | targetExhausted, /* insuff. room in target for conversion */ 119 | sourceIllegal /* source sequence is illegal/malformed */ 120 | } ConversionResult; 121 | 122 | typedef enum { 123 | strictConversion = 0, 124 | lenientConversion 125 | } ConversionFlags; 126 | 127 | ConversionResult ConvertUTF8toUTF32 ( 128 | const UTF8** sourceStart, const UTF8* sourceEnd, 129 | UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); 130 | 131 | ConversionResult ConvertUTF32toUTF8 ( 132 | const UTF32** sourceStart, const UTF32* sourceEnd, 133 | UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); 134 | 135 | } 136 | 137 | #endif /* REPLXX_CONVERT_UTF8_H_INCLUDED */ 138 | 139 | /* --------------------------------------------------------------------- */ 140 | -------------------------------------------------------------------------------- /src/unicodestring.hxx: -------------------------------------------------------------------------------- 1 | #ifndef REPLXX_UNICODESTRING_HXX_INCLUDED 2 | #define REPLXX_UNICODESTRING_HXX_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "conversion.hxx" 10 | 11 | namespace replxx { 12 | 13 | inline bool case_sensitive_equal( char32_t l, char32_t r ) { 14 | return l == r; 15 | } 16 | 17 | inline bool case_insensitive_equal( char32_t l, char32_t r ) { 18 | return towlower( static_cast( l ) ) == towlower( static_cast( r ) ); 19 | } 20 | 21 | class UnicodeString { 22 | public: 23 | typedef std::vector data_buffer_t; 24 | typedef data_buffer_t::const_iterator const_iterator; 25 | typedef data_buffer_t::iterator iterator; 26 | private: 27 | data_buffer_t _data; 28 | public: 29 | UnicodeString() 30 | : _data() { 31 | } 32 | 33 | explicit UnicodeString( std::string const& src ) 34 | : _data() { 35 | assign( src ); 36 | } 37 | 38 | explicit UnicodeString( UnicodeString const& other, int offset, int len = -1 ) 39 | : _data() { 40 | _data.insert( 41 | _data.end(), 42 | other._data.begin() + offset, 43 | len > 0 ? other._data.begin() + offset + len : other._data.end() 44 | ); 45 | } 46 | 47 | explicit UnicodeString( char const* src ) 48 | : _data() { 49 | assign( src ); 50 | } 51 | 52 | explicit UnicodeString( char8_t const* src ) 53 | : UnicodeString( reinterpret_cast( src ) ) { 54 | } 55 | 56 | explicit UnicodeString( char32_t const* src ) 57 | : _data() { 58 | int len( 0 ); 59 | while ( src[len] != 0 ) { 60 | ++ len; 61 | } 62 | _data.assign( src, src + len ); 63 | } 64 | 65 | explicit UnicodeString( char32_t const* src, int len ) 66 | : _data() { 67 | _data.assign( src, src + len ); 68 | } 69 | 70 | explicit UnicodeString( int len ) 71 | : _data() { 72 | _data.resize( len ); 73 | } 74 | 75 | UnicodeString& assign( std::string const& str_ ) { 76 | _data.resize( static_cast( str_.length() ) ); 77 | int len( 0 ); 78 | copyString8to32( _data.data(), static_cast( str_.length() ), len, str_.c_str() ); 79 | _data.resize( len ); 80 | return *this; 81 | } 82 | 83 | UnicodeString& assign( char const* str_ ) { 84 | int byteCount( static_cast( strlen( str_ ) ) ); 85 | _data.resize( byteCount ); 86 | int len( 0 ); 87 | copyString8to32( _data.data(), byteCount, len, str_ ); 88 | _data.resize( len ); 89 | return *this; 90 | } 91 | 92 | UnicodeString& assign( UnicodeString const& other_ ) { 93 | _data = other_._data; 94 | return *this; 95 | } 96 | 97 | explicit UnicodeString( UnicodeString const& ) = default; 98 | UnicodeString& operator = ( UnicodeString const& ) = default; 99 | UnicodeString( UnicodeString&& ) = default; 100 | UnicodeString& operator = ( UnicodeString&& ) = default; 101 | bool operator == ( UnicodeString const& other_ ) const { 102 | return ( _data == other_._data ); 103 | } 104 | 105 | bool operator != ( UnicodeString const& other_ ) const { 106 | return ( _data != other_._data ); 107 | } 108 | 109 | bool operator < ( UnicodeString const& other_ ) const { 110 | return std::lexicographical_compare(begin(), end(), other_.begin(), other_.end()); 111 | } 112 | 113 | UnicodeString& append( UnicodeString const& other ) { 114 | _data.insert( _data.end(), other._data.begin(), other._data.end() ); 115 | return *this; 116 | } 117 | 118 | void push_back( char32_t c_ ) { 119 | _data.push_back( c_ ); 120 | } 121 | 122 | UnicodeString& append( char32_t const* src, int len ) { 123 | _data.insert( _data.end(), src, src + len ); 124 | return *this; 125 | } 126 | 127 | UnicodeString& insert( int pos_, UnicodeString const& str_, int offset_, int len_ ) { 128 | _data.insert( _data.begin() + pos_, str_._data.begin() + offset_, str_._data.begin() + offset_ + len_ ); 129 | return *this; 130 | } 131 | 132 | UnicodeString& insert( int pos_, char32_t c_ ) { 133 | _data.insert( _data.begin() + pos_, c_ ); 134 | return *this; 135 | } 136 | 137 | UnicodeString& erase( int pos_ ) { 138 | _data.erase( _data.begin() + pos_ ); 139 | return *this; 140 | } 141 | 142 | UnicodeString& erase( int pos_, int len_ ) { 143 | _data.erase( _data.begin() + pos_, _data.begin() + pos_ + len_ ); 144 | return *this; 145 | } 146 | 147 | char32_t const* get() const { 148 | return _data.data(); 149 | } 150 | 151 | char32_t* get() { 152 | return _data.data(); 153 | } 154 | 155 | int length() const { 156 | return static_cast( _data.size() ); 157 | } 158 | 159 | void clear( void ) { 160 | _data.clear(); 161 | } 162 | 163 | const char32_t& operator[]( int pos ) const { 164 | assert( ( pos >= 0 ) && ( pos < static_cast( _data.size() ) ) ); 165 | return _data[pos]; 166 | } 167 | 168 | char32_t& operator[]( int pos ) { 169 | assert( ( pos >= 0 ) && ( pos < static_cast( _data.size() ) ) ); 170 | return _data[pos]; 171 | } 172 | 173 | bool starts_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_ ) const { 174 | return ( 175 | ( std::distance( first_, last_ ) <= length() ) 176 | && ( std::equal( first_, last_, _data.begin() ) ) 177 | ); 178 | } 179 | 180 | template 181 | bool starts_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_, BinaryPredicate&& pred ) const { 182 | return ( 183 | ( std::distance( first_, last_ ) <= length() ) 184 | && ( std::equal( first_, last_, _data.begin(), std::forward( pred ) ) ) 185 | ); 186 | } 187 | 188 | bool ends_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_ ) const { 189 | int len( static_cast( std::distance( first_, last_ ) ) ); 190 | return ( 191 | ( len <= length() ) 192 | && ( std::equal( first_, last_, _data.end() - len ) ) 193 | ); 194 | } 195 | 196 | bool is_empty( void ) const { 197 | return ( _data.size() == 0 ); 198 | } 199 | 200 | void swap( UnicodeString& other_ ) { 201 | _data.swap( other_._data ); 202 | } 203 | 204 | const_iterator begin( void ) const { 205 | return ( _data.begin() ); 206 | } 207 | 208 | const_iterator end( void ) const { 209 | return ( _data.end() ); 210 | } 211 | 212 | iterator begin( void ) { 213 | return ( _data.begin() ); 214 | } 215 | 216 | iterator end( void ) { 217 | return ( _data.end() ); 218 | } 219 | 220 | char32_t back( void ) const { 221 | return ( _data.back() ); 222 | } 223 | }; 224 | 225 | } 226 | 227 | #endif 228 | 229 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project( 3 | replxx 4 | # HOMEPAGE_URL "https://github.com/AmokHuginnsson/replxx" 5 | # DESCRIPTION "replxx - Read Evaluate Print Loop library" 6 | VERSION 0.0.4 7 | LANGUAGES CXX C 8 | ) 9 | 10 | if (NOT DEFINED CMAKE_CXX_STANDARD) 11 | set(CMAKE_CXX_STANDARD 11) 12 | endif() 13 | 14 | if (NOT DEFINED CMAKE_C_STANDARD) 15 | set(CMAKE_C_STANDARD 99) 16 | endif() 17 | 18 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) 19 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 20 | set(CMAKE_CXX_EXTENSIONS ON) 21 | 22 | include(CMakePackageConfigHelpers) 23 | include(CMakeDependentOption) 24 | include(GenerateExportHeader) 25 | include(GNUInstallDirs) 26 | 27 | find_package(Threads) 28 | 29 | cmake_dependent_option( 30 | REPLXX_BUILD_EXAMPLES 31 | "Build the examples" ON 32 | "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF 33 | ) 34 | cmake_dependent_option( 35 | REPLXX_BUILD_PACKAGE 36 | "Generate package target" ON 37 | "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF 38 | ) 39 | 40 | if (NOT CMAKE_BUILD_TYPE) 41 | message(AUTHOR_WARNING "CMAKE_BUILD_TYPE not set. Defaulting to Release") 42 | set(CMAKE_BUILD_TYPE Release) 43 | endif() 44 | 45 | # INFO 46 | set(REPLXX_URL_INFO_ABOUT "https://github.com/AmokHuginnsson/replxx") 47 | set(REPLXX_DISPLAY_NAME "replxx") 48 | set(REPLXX_CONTACT "amok@codestation.org") 49 | 50 | set(is-clang $,$>) 51 | set(is-msvc $) 52 | set(is-gnu $) 53 | set(compiler-id-clang-or-gnu $) 54 | 55 | set(coverage-config $,$>) 56 | 57 | set(replxx-source-patterns "src/*.cpp" "src/*.cxx") 58 | 59 | if (CMAKE_VERSION VERSION_GREATER 3.11) 60 | list(INSERT replxx-source-patterns 0 CONFIGURE_DEPENDS) 61 | endif() 62 | 63 | file(GLOB replxx-sources ${replxx-source-patterns}) 64 | 65 | add_library(replxx ${replxx-sources}) 66 | add_library(replxx::replxx ALIAS replxx) 67 | 68 | target_include_directories( 69 | replxx 70 | PUBLIC 71 | $ 72 | $ 73 | PRIVATE 74 | $ 75 | ) 76 | target_compile_definitions( 77 | replxx 78 | PUBLIC 79 | $<$>:REPLXX_STATIC> 80 | $<$:REPLXX_BUILDING_DLL> 81 | PRIVATE 82 | $<$:_CRT_SECURE_NO_WARNINGS=1 /ignore:4503> 83 | ) 84 | target_compile_options( 85 | replxx 86 | PRIVATE 87 | $<$,${compiler-id-clang-or-gnu}>:-fomit-frame-pointer> 88 | $<$,${compiler-id-clang-or-gnu}>:-Os> 89 | $<$,$>:-g -ggdb -g3 -ggdb3> 90 | $<${coverage-config}:-O0 --coverage> 91 | $<${coverage-config}:-fno-inline -fno-default-inline> 92 | $<${coverage-config}:-fno-inline-small-functions> 93 | $<${compiler-id-clang-or-gnu}:-Wall -Wextra> 94 | $<$:-Wno-unknown-pragmas> 95 | ) 96 | if (NOT CMAKE_VERSION VERSION_LESS 3.13) 97 | target_link_options( 98 | replxx 99 | PRIVATE 100 | $<$,$>:-g -ggdb -g3 -ggdb3> 101 | $<${coverage-config}:--coverage> 102 | $<${is-msvc}:/ignore:4099> 103 | ) 104 | else() 105 | # "safest" way prior to 3.13 106 | target_link_libraries( 107 | replxx 108 | PRIVATE 109 | $<${coverage-config}:--coverage> 110 | $<${is-msvc}:/ignore:4099> 111 | ) 112 | endif() 113 | target_link_libraries(replxx PUBLIC Threads::Threads) 114 | set_target_properties(replxx PROPERTIES VERSION ${PROJECT_VERSION}) 115 | 116 | set_property(TARGET replxx PROPERTY DEBUG_POSTFIX -d) 117 | set_property(TARGET replxx PROPERTY RELWITHDEBINFO_POSTFIX -rd) 118 | set_property(TARGET replxx PROPERTY MINSIZEREL_POSTFIX) 119 | if ( NOT BUILD_SHARED_LIBS AND MSVC ) 120 | set_property(TARGET replxx PROPERTY OUTPUT_NAME replxx-static) 121 | endif() 122 | 123 | generate_export_header(replxx) 124 | 125 | configure_package_config_file( 126 | "${PROJECT_SOURCE_DIR}/replxx-config.cmake.in" 127 | "${PROJECT_BINARY_DIR}/replxx-config.cmake" 128 | INSTALL_DESTINATION ${CMAKE_INSTALL_DATADIR}/cmake/replxx 129 | NO_CHECK_REQUIRED_COMPONENTS_MACRO 130 | NO_SET_AND_CHECK_MACRO 131 | ) 132 | 133 | write_basic_package_version_file( 134 | "${PROJECT_BINARY_DIR}/replxx-config-version.cmake" 135 | COMPATIBILITY AnyNewerVersion 136 | ) 137 | 138 | install( 139 | TARGETS replxx EXPORT replxx-targets 140 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 141 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 142 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 143 | ) 144 | 145 | install( 146 | EXPORT replxx-targets 147 | NAMESPACE replxx:: 148 | DESTINATION ${CMAKE_INSTALL_DATADIR}/cmake/replxx 149 | ) 150 | 151 | install( 152 | FILES 153 | "${PROJECT_BINARY_DIR}/replxx-config-version.cmake" 154 | "${PROJECT_BINARY_DIR}/replxx-config.cmake" 155 | DESTINATION 156 | ${CMAKE_INSTALL_DATADIR}/cmake/replxx 157 | ) 158 | 159 | # headers 160 | install( 161 | FILES 162 | include/replxx.hxx 163 | include/replxx.h 164 | DESTINATION 165 | ${CMAKE_INSTALL_INCLUDEDIR} 166 | ) 167 | 168 | if (REPLXX_BUILD_EXAMPLES) 169 | add_executable(replxx-example-cxx-api "") 170 | add_executable(replxx-example-c-api "") 171 | 172 | target_sources( 173 | replxx-example-cxx-api 174 | PRIVATE 175 | examples/cxx-api.cxx 176 | examples/util.c 177 | ) 178 | target_sources( 179 | replxx-example-c-api 180 | PRIVATE 181 | examples/c-api.c 182 | examples/util.c 183 | ) 184 | target_compile_definitions(replxx-example-cxx-api PRIVATE REPLXX_STATIC $<$:_CRT_SECURE_NO_WARNINGS=1>) 185 | target_compile_definitions(replxx-example-c-api PRIVATE REPLXX_STATIC $<$:_CRT_SECURE_NO_WARNINGS=1>) 186 | target_link_options(replxx-example-cxx-api PRIVATE $<$,$>:-g -ggdb -g3 -ggdb3>) 187 | target_link_options(replxx-example-c-api PRIVATE $<$,$>:-g -ggdb -g3 -ggdb3>) 188 | target_link_libraries(replxx-example-cxx-api PRIVATE replxx::replxx) 189 | target_link_libraries( 190 | replxx-example-c-api 191 | PRIVATE 192 | replxx::replxx 193 | $<${compiler-id-clang-or-gnu}:stdc++> 194 | $<${is-clang}:m> 195 | $<${coverage-config}:--coverage> 196 | ) 197 | target_link_libraries( 198 | replxx-example-cxx-api 199 | PRIVATE 200 | $<${coverage-config}:--coverage> 201 | ) 202 | endif() 203 | 204 | if (NOT REPLXX_BUILD_PACKAGE) 205 | return() 206 | endif() 207 | 208 | include(CPack) 209 | 210 | set(CPACK_SET_DESTDIR ON) 211 | 212 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Readline and libedit replacement library") 213 | set(CPACK_PACKAGE_HOMEPAGE_URL "${REPLXX_URL_INFO_ABOUT}") 214 | set(CPACK_PACKAGE_VENDOR "codestation.org") 215 | set(CPACK_PACKAGE_CONTACT "amok@codestation.org") 216 | set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") 217 | 218 | set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") 219 | 220 | set(CPACK_STRIP_FILES "ON") 221 | 222 | set(CPACK_DEBIAN_PACKAGE_SECTION "utilities") 223 | 224 | -------------------------------------------------------------------------------- /examples/c-api.c: -------------------------------------------------------------------------------- 1 | #ifndef __sun 2 | #define _POSIX_C_SOURCE 200809L 3 | #else 4 | #define __EXTENSIONS__ 1 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "replxx.h" 15 | #include "util.h" 16 | 17 | void modify_callback(char** line, int* cursorPosition, void* ud) { 18 | char* s = *line; 19 | char* p = strchr( s, '*' ); 20 | if ( p ) { 21 | int len = (int)strlen( s ); 22 | char* n = *line = calloc( len * 2, 1 ); 23 | int i = (int)( p - s ); 24 | strncpy(n, s, i); 25 | n += i; 26 | strncpy(n, s, i); 27 | n += i; 28 | strncpy(n, p + 1, len - i - 1); 29 | n += ( len - i - 1 ); 30 | strncpy(n, p + 1, len - i - 1); 31 | *cursorPosition *= 2; 32 | free( s ); 33 | } 34 | } 35 | 36 | void completionHook(char const* context, replxx_completions* lc, int* contextLen, void* ud) { 37 | char** examples = (char**)( ud ); 38 | size_t i; 39 | 40 | int utf8ContextLen = context_len( context ); 41 | int prefixLen = (int)strlen( context ) - utf8ContextLen; 42 | *contextLen = utf8str_codepoint_len( context + prefixLen, utf8ContextLen ); 43 | for (i = 0; examples[i] != NULL; ++i) { 44 | if (strncmp(context + prefixLen, examples[i], utf8ContextLen) == 0) { 45 | replxx_add_completion(lc, examples[i]); 46 | } 47 | } 48 | } 49 | 50 | void hintHook(char const* context, replxx_hints* lc, int* contextLen, ReplxxColor* c, void* ud) { 51 | char** examples = (char**)( ud ); 52 | int i; 53 | int utf8ContextLen = context_len( context ); 54 | int prefixLen = (int)strlen( context ) - utf8ContextLen; 55 | *contextLen = utf8str_codepoint_len( context + prefixLen, utf8ContextLen ); 56 | if ( *contextLen > 0 ) { 57 | for (i = 0; examples[i] != NULL; ++i) { 58 | if (strncmp(context + prefixLen, examples[i], utf8ContextLen) == 0) { 59 | replxx_add_hint(lc, examples[i]); 60 | } 61 | } 62 | } 63 | } 64 | 65 | void colorHook( char const* str_, ReplxxColor* colors_, int size_, void* ud ) { 66 | int i = 0; 67 | for ( ; i < size_; ++ i ) { 68 | if ( isdigit( str_[i] ) ) { 69 | colors_[i] = REPLXX_COLOR_BRIGHTMAGENTA; 70 | } 71 | } 72 | if ( ( size_ > 0 ) && ( str_[size_ - 1] == '(' ) ) { 73 | replxx_emulate_key_press( ud, ')' ); 74 | replxx_emulate_key_press( ud, REPLXX_KEY_LEFT ); 75 | } 76 | } 77 | 78 | ReplxxActionResult word_eater( int ignored, void* ud ) { 79 | Replxx* replxx = (Replxx*)ud; 80 | return ( replxx_invoke( replxx, REPLXX_ACTION_KILL_TO_BEGINING_OF_WORD, 0 ) ); 81 | } 82 | 83 | ReplxxActionResult upper_case_line( int ignored, void* ud ) { 84 | Replxx* replxx = (Replxx*)ud; 85 | ReplxxState state; 86 | replxx_get_state( replxx, &state ); 87 | int l = (int)strlen( state.text ); 88 | #ifdef _WIN32 89 | #define strdup _strdup 90 | #endif 91 | char* d = strdup( state.text ); 92 | #undef strdup 93 | for ( int i = 0; i < l; ++ i ) { 94 | d[i] = toupper( d[i] ); 95 | } 96 | state.text = d; 97 | state.cursorPosition /= 2; 98 | replxx_set_state( replxx, &state ); 99 | free( d ); 100 | return ( REPLXX_ACTION_RESULT_CONTINUE ); 101 | } 102 | 103 | char const* recode( char* s ) { 104 | char const* r = s; 105 | while ( *s ) { 106 | if ( *s == '~' ) { 107 | *s = '\n'; 108 | } 109 | ++ s; 110 | } 111 | return ( r ); 112 | } 113 | 114 | void split( char* str_, char** data_, int size_ ) { 115 | int i = 0; 116 | char* p = str_, *o = p; 117 | while ( i < size_ ) { 118 | int last = *p == 0; 119 | if ( ( *p == ',' ) || last ) { 120 | *p = 0; 121 | data_[i ++] = o; 122 | o = p + 1; 123 | if ( last ) { 124 | break; 125 | } 126 | } 127 | ++ p; 128 | } 129 | data_[i] = 0; 130 | } 131 | 132 | int main( int argc, char** argv ) { 133 | #define MAX_EXAMPLE_COUNT 128 134 | char* examples[MAX_EXAMPLE_COUNT + 1] = { 135 | "db", "hello", "hallo", "hans", "hansekogge", "seamann", "quetzalcoatl", "quit", "power", NULL 136 | }; 137 | Replxx* replxx = replxx_init(); 138 | replxx_install_window_change_handler( replxx ); 139 | 140 | int quiet = 0; 141 | char const* prompt = "\x1b[1;32mreplxx\x1b[0m> "; 142 | int installModifyCallback = 0; 143 | int installCompletionCallback = 1; 144 | int installHighlighterCallback = 1; 145 | int installHintsCallback = 1; 146 | int indentMultiline = 0; 147 | while ( argc > 1 ) { 148 | -- argc; 149 | ++ argv; 150 | #ifdef __REPLXX_DEBUG__ 151 | if ( !strcmp( *argv, "--keycodes" ) ) { 152 | replxx_debug_dump_print_codes(); 153 | exit(0); 154 | } 155 | #endif 156 | switch ( (*argv)[0] ) { 157 | case 'b': replxx_set_beep_on_ambiguous_completion( replxx, (*argv)[1] - '0' ); break; 158 | case 'c': replxx_set_completion_count_cutoff( replxx, atoi( (*argv) + 1 ) ); break; 159 | case 'e': replxx_set_complete_on_empty( replxx, (*argv)[1] - '0' ); break; 160 | case 'd': replxx_set_double_tab_completion( replxx, (*argv)[1] - '0' ); break; 161 | case 'h': replxx_set_max_hint_rows( replxx, atoi( (*argv) + 1 ) ); break; 162 | case 'H': replxx_set_hint_delay( replxx, atoi( (*argv) + 1 ) ); break; 163 | case 's': replxx_set_max_history_size( replxx, atoi( (*argv) + 1 ) ); break; 164 | case 'P': replxx_set_preload_buffer( replxx, recode( (*argv) + 1 ) ); break; 165 | case 'I': replxx_set_immediate_completion( replxx, (*argv)[1] - '0' ); break; 166 | case 'u': replxx_set_unique_history( replxx, (*argv)[1] - '0' ); break; 167 | case 'w': replxx_set_word_break_characters( replxx, (*argv) + 1 ); break; 168 | case 'm': replxx_set_no_color( replxx, (*argv)[1] - '0' ); break; 169 | case 'i': replxx_set_ignore_case( replxx, (*argv)[1] - '0' ); break; 170 | case 'n': indentMultiline = (*argv)[1] - '0'; break; 171 | case 'B': replxx_enable_bracketed_paste( replxx ); break; 172 | case 'p': prompt = recode( (*argv) + 1 ); break; 173 | case 'q': quiet = atoi( (*argv) + 1 ); break; 174 | case 'M': installModifyCallback = atoi( (*argv) + 1 ); break; 175 | case 'C': installCompletionCallback = 0; break; 176 | case 'S': installHighlighterCallback = 0; break; 177 | case 'N': installHintsCallback = 0; break; 178 | case 'x': split( (*argv) + 1, examples, MAX_EXAMPLE_COUNT ); break; 179 | } 180 | 181 | } 182 | 183 | replxx_set_indent_multiline( replxx, indentMultiline ); 184 | const char* file = "./replxx_history.txt"; 185 | 186 | replxx_history_load( replxx, file ); 187 | if ( installModifyCallback ) { 188 | replxx_set_modify_callback( replxx, modify_callback, 0 ); 189 | } 190 | if ( installCompletionCallback ) { 191 | replxx_set_completion_callback( replxx, completionHook, examples ); 192 | } 193 | if ( installHighlighterCallback ) { 194 | replxx_set_highlighter_callback( replxx, colorHook, replxx ); 195 | } 196 | if ( installHintsCallback ) { 197 | replxx_set_hint_callback( replxx, hintHook, examples ); 198 | } 199 | replxx_bind_key( replxx, '.', word_eater, replxx ); 200 | replxx_bind_key( replxx, REPLXX_KEY_F2, upper_case_line, replxx ); 201 | 202 | printf("starting...\n"); 203 | 204 | while (1) { 205 | char const* result = NULL; 206 | do { 207 | result = replxx_input( replxx, prompt ); 208 | } while ( ( result == NULL ) && ( errno == EAGAIN ) ); 209 | 210 | if (result == NULL) { 211 | printf("\n"); 212 | break; 213 | } else if (!strncmp(result, "/history", 9)) { 214 | /* Display the current history. */ 215 | int index = 0; 216 | int size = replxx_history_size( replxx ); 217 | ReplxxHistoryScan* hs = replxx_history_scan_start( replxx ); 218 | ReplxxHistoryEntry he; 219 | for ( ; replxx_history_scan_next( replxx, hs, &he ) == 0; ++index ) { 220 | replxx_print( replxx, "%4d: %s\n", index, he.text ); 221 | } 222 | replxx_history_scan_stop( replxx, hs ); 223 | } else if (!strncmp(result, "/unique", 8)) { 224 | replxx_set_unique_history( replxx, 1 ); 225 | } else if (!strncmp(result, "/eb", 4)) { 226 | replxx_enable_bracketed_paste( replxx ); 227 | } else if (!strncmp(result, "/db", 4)) { 228 | replxx_disable_bracketed_paste( replxx ); 229 | } 230 | if (*result != '\0') { 231 | replxx_print( replxx, quiet ? "%s\n" : "thanks for the input: %s\n", result ); 232 | replxx_history_add( replxx, result ); 233 | } 234 | } 235 | replxx_history_save( replxx, file ); 236 | printf( "Exiting Replxx\n" ); 237 | replxx_end( replxx ); 238 | } 239 | 240 | -------------------------------------------------------------------------------- /src/ConvertUTF.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2001-2004 Unicode, Inc. 3 | * 4 | * Disclaimer 5 | * 6 | * This source code is provided as is by Unicode, Inc. No claims are 7 | * made as to fitness for any particular purpose. No warranties of any 8 | * kind are expressed or implied. The recipient agrees to determine 9 | * applicability of information provided. If this file has been 10 | * purchased on magnetic or optical media from Unicode, Inc., the 11 | * sole remedy for any claim will be exchange of defective media 12 | * within 90 days of receipt. 13 | * 14 | * Limitations on Rights to Redistribute This Code 15 | * 16 | * Unicode, Inc. hereby grants the right to freely use the information 17 | * supplied in this file in the creation of products supporting the 18 | * Unicode Standard, and to make copies of this file in any form 19 | * for internal or external distribution as long as this notice 20 | * remains attached. 21 | */ 22 | 23 | /* --------------------------------------------------------------------- 24 | 25 | Conversions between UTF32, UTF-16, and UTF-8. Source code file. 26 | Author: Mark E. Davis, 1994. 27 | Rev History: Rick McGowan, fixes & updates May 2001. 28 | Sept 2001: fixed const & error conditions per 29 | mods suggested by S. Parent & A. Lillich. 30 | June 2002: Tim Dodd added detection and handling of incomplete 31 | source sequences, enhanced error detection, added casts 32 | to eliminate compiler warnings. 33 | July 2003: slight mods to back out aggressive FFFE detection. 34 | Jan 2004: updated switches in from-UTF8 conversions. 35 | Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. 36 | 37 | See the header file "ConvertUTF.h" for complete documentation. 38 | 39 | ------------------------------------------------------------------------ */ 40 | 41 | #include "ConvertUTF.h" 42 | #ifdef CVTUTF_DEBUG 43 | #include 44 | #endif 45 | 46 | namespace replxx { 47 | 48 | #define UNI_SUR_HIGH_START (UTF32)0xD800 49 | #define UNI_SUR_LOW_END (UTF32)0xDFFF 50 | 51 | /* --------------------------------------------------------------------- */ 52 | 53 | /* 54 | * Index into the table below with the first byte of a UTF-8 sequence to 55 | * get the number of trailing bytes that are supposed to follow it. 56 | * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is 57 | * left as-is for anyone who may want to do such conversion, which was 58 | * allowed in earlier algorithms. 59 | */ 60 | static const char trailingBytesForUTF8[256] = { 61 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 62 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 63 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 64 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 65 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 66 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 67 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 68 | 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 69 | }; 70 | 71 | /* 72 | * Magic values subtracted from a buffer value during UTF8 conversion. 73 | * This table contains as many values as there might be trailing bytes 74 | * in a UTF-8 sequence. 75 | */ 76 | static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 77 | 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; 78 | 79 | /* 80 | * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed 81 | * into the first byte, depending on how many bytes follow. There are 82 | * as many entries in this table as there are UTF-8 sequence types. 83 | * (I.e., one byte sequence, two byte... etc.). Remember that sequencs 84 | * for *legal* UTF-8 will be 4 or fewer bytes total. 85 | */ 86 | static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; 87 | 88 | /* --------------------------------------------------------------------- */ 89 | 90 | /* The interface converts a whole buffer to avoid function-call overhead. 91 | * Constants have been gathered. Loops & conditionals have been removed as 92 | * much as possible for efficiency, in favor of drop-through switches. 93 | * (See "Note A" at the bottom of the file for equivalent code.) 94 | * If your compiler supports it, the "isLegalUTF8" call can be turned 95 | * into an inline function. 96 | */ 97 | 98 | /* --------------------------------------------------------------------- */ 99 | 100 | /* 101 | * Utility routine to tell whether a sequence of bytes is legal UTF-8. 102 | * This must be called with the length pre-determined by the first byte. 103 | * If not calling this from ConvertUTF8to*, then the length can be set by: 104 | * length = trailingBytesForUTF8[*source]+1; 105 | * and the sequence is illegal right away if there aren't that many bytes 106 | * available. 107 | * If presented with a length > 4, this returns false. The Unicode 108 | * definition of UTF-8 goes up to 4-byte sequences. 109 | */ 110 | 111 | static bool isLegalUTF8(const UTF8 *source, int length) { 112 | UTF8 a; 113 | const UTF8 *srcptr = source+length; 114 | switch (length) { 115 | default: return false; 116 | /* Everything else falls through when "true"... */ 117 | case 4: { if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; } /* fall through */ 118 | case 3: { if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; } /* fall through */ 119 | case 2: { 120 | if ((a = (*--srcptr)) > 0xBF) return false; 121 | 122 | switch (*source) { 123 | /* no fall-through in this inner switch */ 124 | case 0xE0: if (a < 0xA0) return false; break; 125 | case 0xED: if (a > 0x9F) return false; break; 126 | case 0xF0: if (a < 0x90) return false; break; 127 | case 0xF4: if (a > 0x8F) return false; break; 128 | default: if (a < 0x80) return false; 129 | } 130 | } /* fall through */ 131 | case 1: { if (*source >= 0x80 && *source < 0xC2) return false; } /* fall through */ 132 | } 133 | if (*source > 0xF4) return false; 134 | return true; 135 | } 136 | 137 | /* --------------------------------------------------------------------- */ 138 | 139 | ConversionResult ConvertUTF32toUTF8 ( 140 | const UTF32** sourceStart, const UTF32* sourceEnd, 141 | UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { 142 | ConversionResult result = conversionOK; 143 | const UTF32* source = *sourceStart; 144 | UTF8* target = *targetStart; 145 | while (source < sourceEnd) { 146 | UTF32 ch; 147 | unsigned short bytesToWrite = 0; 148 | const UTF32 byteMask = 0xBF; 149 | const UTF32 byteMark = 0x80; 150 | ch = *source++; 151 | if (flags == strictConversion ) { 152 | /* UTF-16 surrogate values are illegal in UTF-32 */ 153 | if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { 154 | --source; /* return to the illegal value itself */ 155 | result = sourceIllegal; 156 | break; 157 | } 158 | } 159 | /* 160 | * Figure out how many bytes the result will require. Turn any 161 | * illegally large UTF32 things (> Plane 17) into replacement chars. 162 | */ 163 | if (ch < (UTF32)0x80) { bytesToWrite = 1; 164 | } else if (ch < (UTF32)0x800) { bytesToWrite = 2; 165 | } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; 166 | } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; 167 | } else { bytesToWrite = 3; 168 | ch = UNI_REPLACEMENT_CHAR; 169 | result = sourceIllegal; 170 | } 171 | 172 | target += bytesToWrite; 173 | if (target > targetEnd) { 174 | --source; /* Back up source pointer! */ 175 | target -= bytesToWrite; result = targetExhausted; break; 176 | } 177 | switch (bytesToWrite) { /* note: everything falls through. */ 178 | case 4: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */ 179 | case 3: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */ 180 | case 2: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */ 181 | case 1: { *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); } /* fall through */ 182 | } 183 | target += bytesToWrite; 184 | } 185 | *sourceStart = source; 186 | *targetStart = target; 187 | return result; 188 | } 189 | 190 | /* --------------------------------------------------------------------- */ 191 | 192 | ConversionResult ConvertUTF8toUTF32 ( 193 | const UTF8** sourceStart, const UTF8* sourceEnd, 194 | UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { 195 | ConversionResult result = conversionOK; 196 | const UTF8* source = *sourceStart; 197 | UTF32* target = *targetStart; 198 | while (source < sourceEnd) { 199 | UTF32 ch = 0; 200 | unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; 201 | if (source + extraBytesToRead >= sourceEnd) { 202 | result = sourceExhausted; break; 203 | } 204 | /* Do this check whether lenient or strict */ 205 | if (! isLegalUTF8(source, extraBytesToRead+1)) { 206 | result = sourceIllegal; 207 | break; 208 | } 209 | /* 210 | * The cases all fall through. See "Note A" below. 211 | */ 212 | switch (extraBytesToRead) { 213 | case 5: { ch += *source++; ch <<= 6; } /* fall through */ 214 | case 4: { ch += *source++; ch <<= 6; } /* fall through */ 215 | case 3: { ch += *source++; ch <<= 6; } /* fall through */ 216 | case 2: { ch += *source++; ch <<= 6; } /* fall through */ 217 | case 1: { ch += *source++; ch <<= 6; } /* fall through */ 218 | case 0: { ch += *source++; } /* fall through */ 219 | } 220 | ch -= offsetsFromUTF8[extraBytesToRead]; 221 | 222 | if (target >= targetEnd) { 223 | source -= (extraBytesToRead+1); /* Back up the source pointer! */ 224 | result = targetExhausted; break; 225 | } 226 | if (ch <= UNI_MAX_LEGAL_UTF32) { 227 | /* 228 | * UTF-16 surrogate values are illegal in UTF-32, and anything 229 | * over Plane 17 (> 0x10FFFF) is illegal. 230 | */ 231 | if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { 232 | if (flags == strictConversion) { 233 | source -= (extraBytesToRead+1); /* return to the illegal value itself */ 234 | result = sourceIllegal; 235 | break; 236 | } else { 237 | *target++ = UNI_REPLACEMENT_CHAR; 238 | } 239 | } else { 240 | *target++ = ch; 241 | } 242 | } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ 243 | result = sourceIllegal; 244 | *target++ = UNI_REPLACEMENT_CHAR; 245 | } 246 | } 247 | *sourceStart = source; 248 | *targetStart = target; 249 | return result; 250 | } 251 | 252 | } 253 | 254 | /* --------------------------------------------------------------------- 255 | 256 | Note A. 257 | The fall-through switches in UTF-8 reading code save a 258 | temp variable, some decrements & conditionals. The switches 259 | are equivalent to the following loop: 260 | { 261 | int tmpBytesToRead = extraBytesToRead+1; 262 | do { 263 | ch += *source++; 264 | --tmpBytesToRead; 265 | if (tmpBytesToRead) ch <<= 6; 266 | } while (tmpBytesToRead > 0); 267 | } 268 | In UTF-8 writing code, the switches on "bytesToWrite" are 269 | similarly unrolled loops. 270 | 271 | --------------------------------------------------------------------- */ 272 | -------------------------------------------------------------------------------- /src/history.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifndef _WIN32 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #endif /* _WIN32 */ 15 | 16 | #include "replxx.hxx" 17 | #include "history.hxx" 18 | 19 | using namespace std; 20 | 21 | namespace replxx { 22 | 23 | namespace { 24 | void delete_ReplxxHistoryScanImpl( Replxx::HistoryScanImpl* impl_ ) { 25 | delete impl_; 26 | } 27 | static int const ETB = 0x17; 28 | } 29 | 30 | static int const REPLXX_DEFAULT_HISTORY_MAX_LEN( 1000 ); 31 | 32 | Replxx::HistoryScan::HistoryScan( impl_t impl_ ) 33 | : _impl( std::move( impl_ ) ) { 34 | } 35 | 36 | bool Replxx::HistoryScan::next( void ) { 37 | return ( _impl->next() ); 38 | } 39 | 40 | Replxx::HistoryScanImpl::HistoryScanImpl( History::entries_t const& entries_ ) 41 | : _entries( entries_ ) 42 | , _it( _entries.end() ) 43 | , _utf8Cache() 44 | , _entryCache( std::string(), std::string() ) 45 | , _cacheValid( false ) { 46 | } 47 | 48 | Replxx::HistoryEntry const& Replxx::HistoryScan::get( void ) const { 49 | return ( _impl->get() ); 50 | } 51 | 52 | bool Replxx::HistoryScanImpl::next( void ) { 53 | if ( _it == _entries.end() ) { 54 | _it = _entries.begin(); 55 | } else { 56 | ++ _it; 57 | } 58 | _cacheValid = false; 59 | return ( _it != _entries.end() ); 60 | } 61 | 62 | Replxx::HistoryEntry const& Replxx::HistoryScanImpl::get( void ) const { 63 | if ( _cacheValid ) { 64 | return ( _entryCache ); 65 | } 66 | _utf8Cache.assign( _it->text() ); 67 | _entryCache = Replxx::HistoryEntry( _it->timestamp(), _utf8Cache.get() ); 68 | _cacheValid = true; 69 | return ( _entryCache ); 70 | } 71 | 72 | Replxx::HistoryScan::impl_t History::scan( void ) const { 73 | return ( Replxx::HistoryScan::impl_t( new Replxx::HistoryScanImpl( _entries ), delete_ReplxxHistoryScanImpl ) ); 74 | } 75 | 76 | History::History( void ) 77 | : _entries() 78 | , _maxSize( REPLXX_DEFAULT_HISTORY_MAX_LEN ) 79 | , _current( _entries.begin() ) 80 | , _yankPos( _entries.end() ) 81 | , _previous( _entries.begin() ) 82 | , _recallMostRecent( false ) 83 | , _unique( true ) { 84 | } 85 | 86 | void History::add( UnicodeString const& line, std::string const& when ) { 87 | if ( _maxSize <= 0 ) { 88 | return; 89 | } 90 | if ( ! _entries.empty() && ( line == _entries.back().text() ) ) { 91 | _entries.back() = Entry( now_ms_str(), line ); 92 | return; 93 | } 94 | remove_duplicate( line ); 95 | trim_to_max_size(); 96 | _entries.emplace_back( when, line ); 97 | _locations.insert( make_pair( line, last() ) ); 98 | if ( _current == _entries.end() ) { 99 | _current = last(); 100 | } 101 | _yankPos = _entries.end(); 102 | } 103 | 104 | #ifndef _WIN32 105 | class FileLock { 106 | std::string _path; 107 | int _lockFd; 108 | public: 109 | FileLock( std::string const& name_ ) 110 | : _path( name_ + ".lock" ) 111 | , _lockFd( ::open( _path.c_str(), O_CREAT | O_RDWR, 0600 ) ) { 112 | static_cast( ::lockf( _lockFd, F_LOCK, 0 ) == 0 ); 113 | } 114 | ~FileLock( void ) { 115 | static_cast( ::lockf( _lockFd, F_ULOCK, 0 ) == 0 ); 116 | ::close( _lockFd ); 117 | ::unlink( _path.c_str() ); 118 | return; 119 | } 120 | }; 121 | #endif 122 | 123 | bool History::save( std::string const& filename, bool sync_ ) { 124 | #ifndef _WIN32 125 | mode_t old_umask = umask( S_IXUSR | S_IRWXG | S_IRWXO ); 126 | FileLock fileLock( filename ); 127 | #endif 128 | entries_t entries; 129 | locations_t locations; 130 | if ( ! sync_ ) { 131 | entries.swap( _entries ); 132 | locations.swap( _locations ); 133 | _entries = entries; 134 | reset_iters(); 135 | } 136 | /* scope for ifstream object auto-close */ { 137 | ifstream histFile( filename ); 138 | if ( histFile ) { 139 | do_load( histFile ); 140 | } 141 | } 142 | sort(); 143 | remove_duplicates(); 144 | trim_to_max_size(); 145 | ofstream histFile( filename ); 146 | if ( ! histFile ) { 147 | return ( false ); 148 | } 149 | #ifndef _WIN32 150 | umask( old_umask ); 151 | chmod( filename.c_str(), S_IRUSR | S_IWUSR ); 152 | #endif 153 | save( histFile ); 154 | if ( ! sync_ ) { 155 | _entries = std::move( entries ); 156 | _locations = std::move( locations ); 157 | } 158 | reset_iters(); 159 | return ( true ); 160 | } 161 | 162 | void History::save( std::ostream& histFile ) { 163 | Utf8String utf8; 164 | UnicodeString us; 165 | for ( Entry& h : _entries ) { 166 | h.reset_scratch(); 167 | if ( ! h.text().is_empty() ) { 168 | us.assign( h.text() ); 169 | std::replace( us.begin(), us.end(), char32_t( '\n' ), char32_t( ETB ) ); 170 | utf8.assign( us ); 171 | histFile << "### " << h.timestamp() << "\n" << utf8.get() << endl; 172 | } 173 | } 174 | } 175 | 176 | namespace { 177 | 178 | bool is_timestamp( std::string const& s ) { 179 | static char const TIMESTAMP_PATTERN[] = "### dddd-dd-dd dd:dd:dd.ddd"; 180 | static int const TIMESTAMP_LENGTH( sizeof ( TIMESTAMP_PATTERN ) - 1 ); 181 | if ( s.length() != TIMESTAMP_LENGTH ) { 182 | return ( false ); 183 | } 184 | for ( int i( 0 ); i < TIMESTAMP_LENGTH; ++ i ) { 185 | if ( TIMESTAMP_PATTERN[i] == 'd' ) { 186 | if ( ! isdigit( s[i] ) ) { 187 | return ( false ); 188 | } 189 | } else if ( s[i] != TIMESTAMP_PATTERN[i] ) { 190 | return ( false ); 191 | } 192 | } 193 | return ( true ); 194 | } 195 | 196 | } 197 | 198 | void History::do_load( std::istream& histFile ) { 199 | string line; 200 | string when( "0000-00-00 00:00:00.000" ); 201 | UnicodeString us; 202 | while ( getline( histFile, line ).good() ) { 203 | string::size_type eol( line.find_first_of( "\r\n" ) ); 204 | if ( eol != string::npos ) { 205 | line.erase( eol ); 206 | } 207 | if ( is_timestamp( line ) ) { 208 | when.assign( line, 4, std::string::npos ); 209 | continue; 210 | } 211 | if ( ! line.empty() ) { 212 | us.assign( line ); 213 | std::replace( us.begin(), us.end(), char32_t( ETB ), char32_t( '\n' ) ); 214 | _entries.emplace_back( when, us ); 215 | } 216 | } 217 | } 218 | 219 | bool History::load( std::string const& filename ) { 220 | ifstream histFile( filename ); 221 | if ( ! histFile ) { 222 | clear(); 223 | return false; 224 | } 225 | load(histFile); 226 | return true; 227 | } 228 | 229 | void History::load( std::istream& histFile ) { 230 | clear(); 231 | do_load( histFile ); 232 | sort(); 233 | remove_duplicates(); 234 | trim_to_max_size(); 235 | _previous = _current = last(); 236 | _yankPos = _entries.end(); 237 | } 238 | 239 | void History::sort( void ) { 240 | typedef std::vector sortable_entries_t; 241 | _locations.clear(); 242 | sortable_entries_t sortableEntries( _entries.begin(), _entries.end() ); 243 | std::stable_sort( sortableEntries.begin(), sortableEntries.end() ); 244 | _entries.clear(); 245 | _entries.insert( _entries.begin(), sortableEntries.begin(), sortableEntries.end() ); 246 | } 247 | 248 | void History::clear( void ) { 249 | _locations.clear(); 250 | _entries.clear(); 251 | _current = _entries.begin(); 252 | _recallMostRecent = false; 253 | } 254 | 255 | void History::set_max_size( int size_ ) { 256 | if ( size_ >= 0 ) { 257 | _maxSize = size_; 258 | trim_to_max_size(); 259 | } 260 | } 261 | 262 | void History::reset_yank_iterator( void ) { 263 | _yankPos = _entries.end(); 264 | } 265 | 266 | bool History::next_yank_position( void ) { 267 | bool resetYankSize( false ); 268 | if ( _yankPos == _entries.end() ) { 269 | resetYankSize = true; 270 | } 271 | if ( ( _yankPos != _entries.begin() ) && ( _yankPos != _entries.end() ) ) { 272 | -- _yankPos; 273 | } else { 274 | _yankPos = moved( _entries.end(), -2 ); 275 | } 276 | return ( resetYankSize ); 277 | } 278 | 279 | bool History::move( bool up_ ) { 280 | bool doRecall( _recallMostRecent && ! up_ ); 281 | if ( doRecall ) { 282 | _current = _previous; // emulate Windows down-arrow 283 | } 284 | _recallMostRecent = false; 285 | return ( doRecall || move( _current, up_ ? -1 : 1 ) ); 286 | } 287 | 288 | void History::jump( bool start_, bool reset_ ) { 289 | if ( start_ ) { 290 | _current = _entries.begin(); 291 | } else { 292 | _current = last(); 293 | } 294 | if ( reset_ ) { 295 | _recallMostRecent = false; 296 | } 297 | } 298 | 299 | void History::save_pos( void ) { 300 | _previous = _current; 301 | } 302 | 303 | void History::restore_pos( void ) { 304 | _current = _previous; 305 | } 306 | 307 | bool History::common_prefix_search( UnicodeString const& prefix_, int prefixSize_, bool back_, bool ignoreCase ) { 308 | int step( back_ ? -1 : 1 ); 309 | entries_t::iterator it( moved( _current, step, true ) ); 310 | bool lowerCaseContext( std::none_of( prefix_.begin(), prefix_.end(), []( char32_t x ) { return iswupper( static_cast( x ) ); } ) ); 311 | while ( it != _current ) { 312 | if ( it->text().starts_with( prefix_.begin(), prefix_.begin() + prefixSize_, ignoreCase && lowerCaseContext ? case_insensitive_equal : case_sensitive_equal ) ) { 313 | _current = it; 314 | commit_index(); 315 | return ( true ); 316 | } 317 | move( it, step, true ); 318 | } 319 | return ( false ); 320 | } 321 | 322 | bool History::move( entries_t::iterator& it_, int by_, bool wrapped_ ) { 323 | if ( by_ > 0 ) { 324 | for ( int i( 0 ); i < by_; ++ i ) { 325 | ++ it_; 326 | if ( it_ != _entries.end() ) { 327 | } else if ( wrapped_ ) { 328 | it_ = _entries.begin(); 329 | } else { 330 | -- it_; 331 | return ( false ); 332 | } 333 | } 334 | } else { 335 | for ( int i( 0 ); i > by_; -- i ) { 336 | if ( it_ != _entries.begin() ) { 337 | -- it_; 338 | } else if ( wrapped_ ) { 339 | it_ = last(); 340 | } else { 341 | return ( false ); 342 | } 343 | } 344 | } 345 | return ( true ); 346 | } 347 | 348 | History::entries_t::iterator History::moved( entries_t::iterator it_, int by_, bool wrapped_ ) { 349 | move( it_, by_, wrapped_ ); 350 | return ( it_ ); 351 | } 352 | 353 | void History::erase( entries_t::iterator it_ ) { 354 | bool invalidated( it_ == _current ); 355 | _locations.erase( it_->text() ); 356 | it_ = _entries.erase( it_ ); 357 | if ( invalidated ) { 358 | _current = it_; 359 | } 360 | if ( ( _current == _entries.end() ) && ! _entries.empty() ) { 361 | -- _current; 362 | } 363 | _yankPos = _entries.end(); 364 | _previous = _current; 365 | } 366 | 367 | void History::trim_to_max_size( void ) { 368 | while ( size() > _maxSize ) { 369 | erase( _entries.begin() ); 370 | } 371 | } 372 | 373 | void History::remove_duplicate( UnicodeString const& line_ ) { 374 | if ( ! _unique ) { 375 | return; 376 | } 377 | locations_t::iterator it( _locations.find( line_ ) ); 378 | if ( it == _locations.end() ) { 379 | return; 380 | } 381 | erase( it->second ); 382 | } 383 | 384 | void History::remove_duplicates( void ) { 385 | if ( ! _unique ) { 386 | return; 387 | } 388 | _locations.clear(); 389 | typedef std::pair locations_insertion_result_t; 390 | for ( entries_t::iterator it( _entries.begin() ), end( _entries.end() ); it != end; ++ it ) { 391 | it->reset_scratch(); 392 | locations_insertion_result_t locationsInsertionResult( _locations.insert( make_pair( it->text(), it ) ) ); 393 | if ( ! locationsInsertionResult.second ) { 394 | _entries.erase( locationsInsertionResult.first->second ); 395 | locationsInsertionResult.first->second = it; 396 | } 397 | } 398 | } 399 | 400 | void History::update_last( UnicodeString const& line_ ) { 401 | if ( _unique ) { 402 | _locations.erase( _entries.back().text() ); 403 | remove_duplicate( line_ ); 404 | _locations.insert( make_pair( line_, last() ) ); 405 | } 406 | _entries.back() = Entry( now_ms_str(), line_ ); 407 | } 408 | 409 | void History::drop_last( void ) { 410 | reset_current_scratch(); 411 | erase( last() ); 412 | } 413 | 414 | bool History::is_last( void ) { 415 | return ( _current == last() ); 416 | } 417 | 418 | History::entries_t::iterator History::last( void ) { 419 | return ( moved( _entries.end(), -1 ) ); 420 | } 421 | 422 | void History::reset_iters( void ) { 423 | _previous = _current = last(); 424 | _yankPos = _entries.end(); 425 | } 426 | 427 | } 428 | 429 | -------------------------------------------------------------------------------- /src/replxx_impl.hxx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org) 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef HAVE_REPLXX_REPLXX_IMPL_HXX_INCLUDED 32 | #define HAVE_REPLXX_REPLXX_IMPL_HXX_INCLUDED 1 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #include "replxx.hxx" 45 | #include "history.hxx" 46 | #include "killring.hxx" 47 | #include "utf8string.hxx" 48 | #include "prompt.hxx" 49 | 50 | namespace replxx { 51 | 52 | class Replxx::ReplxxImpl { 53 | public: 54 | class Completion { 55 | UnicodeString _text; 56 | Replxx::Color _color; 57 | public: 58 | Completion( UnicodeString const& text_, Replxx::Color color_ ) 59 | : _text( text_ ) 60 | , _color( color_ ) { 61 | } 62 | Completion( Replxx::Completion const& completion_ ) 63 | : _text( completion_.text() ) 64 | , _color( completion_.color() ) { 65 | } 66 | Completion( Completion const& ) = default; 67 | Completion& operator = ( Completion const& ) = default; 68 | Completion( Completion&& ) = default; 69 | Completion& operator = ( Completion&& ) = default; 70 | UnicodeString const& text( void ) const { 71 | return ( _text ); 72 | } 73 | Replxx::Color color( void ) const { 74 | return ( _color ); 75 | } 76 | }; 77 | typedef std::vector completions_t; 78 | typedef std::vector data_t; 79 | typedef std::vector hints_t; 80 | typedef std::unique_ptr utf8_buffer_t; 81 | typedef std::unique_ptr input_buffer_t; 82 | typedef std::vector display_t; 83 | typedef std::deque key_presses_t; 84 | typedef std::deque messages_t; 85 | enum class HINT_ACTION { 86 | REGENERATE, 87 | REPAINT, 88 | TRIM, 89 | SKIP 90 | }; 91 | typedef std::unordered_map named_actions_t; 92 | typedef Replxx::ACTION_RESULT ( ReplxxImpl::* key_press_handler_raw_t )( char32_t ); 93 | typedef std::unordered_map key_press_handlers_t; 94 | private: 95 | typedef int long long unsigned action_trait_t; 96 | static action_trait_t const NOOP = 0; 97 | static action_trait_t const WANT_REFRESH = 1; 98 | static action_trait_t const MOVE_CURSOR = 2; 99 | static action_trait_t const RESET_KILL_ACTION = 4; 100 | static action_trait_t const SET_KILL_ACTION = 8; 101 | static action_trait_t const DONT_RESET_PREFIX = 16; 102 | static action_trait_t const DONT_RESET_COMPLETIONS = 32; 103 | static action_trait_t const HISTORY_RECALL_MOST_RECENT = 64; 104 | static action_trait_t const DONT_RESET_HIST_YANK_INDEX = 128; 105 | private: 106 | mutable Utf8String _utf8Buffer; 107 | UnicodeString _data; 108 | int _pos; // character position in buffer ( 0 <= _pos <= _data[_line].length() ) 109 | display_t _display; 110 | int _displayInputLength; 111 | UnicodeString _hint; 112 | int _prefix; // prefix length used in common prefix search 113 | int _hintSelection; // Currently selected hint. 114 | History _history; 115 | KillRing _killRing; 116 | int long long _lastRefreshTime; 117 | bool _refreshSkipped; 118 | int _lastYankSize; 119 | int _maxHintRows; 120 | int _hintDelay; 121 | std::string _wordBreakChars; 122 | std::string _subwordBreakChars; 123 | int _completionCountCutoff; 124 | bool _overwrite; 125 | bool _doubleTabCompletion; 126 | bool _completeOnEmpty; 127 | bool _beepOnAmbiguousCompletion; 128 | bool _immediateCompletion; 129 | bool _bracketedPaste; 130 | bool _noColor; 131 | bool _indentMultiline; 132 | named_actions_t _namedActions; 133 | key_press_handlers_t _keyPressHandlers; 134 | Terminal _terminal; 135 | std::thread::id _currentThread; 136 | Prompt _prompt; 137 | Replxx::modify_callback_t _modifyCallback; 138 | Replxx::completion_callback_t _completionCallback; 139 | Replxx::highlighter_callback_t _highlighterCallback; 140 | Replxx::hint_callback_t _hintCallback; 141 | key_presses_t _keyPresses; 142 | messages_t _messages; 143 | std::string _asyncPrompt; 144 | bool _updatePrompt; 145 | completions_t _completions; 146 | int _completionContextLength; 147 | int _completionSelection; 148 | std::string _preloadedBuffer; // used with set_preload_buffer 149 | std::string _errorMessage; 150 | UnicodeString _previousSearchText; // remembered across invocations of replxx_input() 151 | bool _modifiedState; 152 | Replxx::Color _hintColor; 153 | hints_t _hintsCache; 154 | int _hintContextLenght; 155 | Utf8String _hintSeed; 156 | bool _hasNewlines; 157 | int _oldPos; 158 | bool _moveCursor; 159 | bool _ignoreCase; 160 | mutable std::mutex _mutex; 161 | public: 162 | ReplxxImpl( FILE*, FILE*, FILE* ); 163 | virtual ~ReplxxImpl( void ); 164 | void set_modify_callback( Replxx::modify_callback_t const& fn ); 165 | void set_completion_callback( Replxx::completion_callback_t const& fn ); 166 | void set_highlighter_callback( Replxx::highlighter_callback_t const& fn ); 167 | void set_hint_callback( Replxx::hint_callback_t const& fn ); 168 | char const* input( std::string const& prompt ); 169 | void history_add( std::string const& line ); 170 | bool history_sync( std::string const& filename ); 171 | bool history_save( std::string const& filename ); 172 | void history_save( std::ostream& out ); 173 | bool history_load( std::string const& filename ); 174 | void history_load( std::istream& in ); 175 | void history_clear( void ); 176 | Replxx::HistoryScan::impl_t history_scan( void ) const; 177 | int history_size( void ) const; 178 | void set_preload_buffer(std::string const& preloadText); 179 | void set_word_break_characters( char const* wordBreakers ); 180 | void set_subword_break_characters( char const* subwordBreakers ); 181 | void set_max_hint_rows( int count ); 182 | void set_hint_delay( int milliseconds ); 183 | void set_double_tab_completion( bool val ); 184 | void set_complete_on_empty( bool val ); 185 | void set_beep_on_ambiguous_completion( bool val ); 186 | void set_immediate_completion( bool val ); 187 | void set_unique_history( bool ); 188 | void set_no_color( bool val ); 189 | void set_indent_multiline( bool val ); 190 | void set_max_history_size( int len ); 191 | void set_completion_count_cutoff( int len ); 192 | int install_window_change_handler( void ); 193 | void enable_bracketed_paste( void ); 194 | void disable_bracketed_paste( void ); 195 | void print( char const*, int ); 196 | void set_prompt( std::string prompt ); 197 | Replxx::ACTION_RESULT clear_screen( char32_t ); 198 | void emulate_key_press( char32_t ); 199 | Replxx::ACTION_RESULT invoke( Replxx::ACTION, char32_t ); 200 | void bind_key( char32_t, Replxx::key_press_handler_t ); 201 | void bind_key_internal( char32_t, char const* ); 202 | Replxx::State get_state( void ) const; 203 | void set_state( Replxx::State const& ); 204 | void set_ignore_case( bool val ); 205 | private: 206 | ReplxxImpl( ReplxxImpl const& ) = delete; 207 | ReplxxImpl& operator = ( ReplxxImpl const& ) = delete; 208 | private: 209 | void preload_puffer( char const* preloadText ); 210 | int get_input_line( void ); 211 | Replxx::ACTION_RESULT action( action_trait_t, key_press_handler_raw_t const&, char32_t ); 212 | Replxx::ACTION_RESULT insert_character( char32_t ); 213 | Replxx::ACTION_RESULT new_line( char32_t ); 214 | Replxx::ACTION_RESULT go_to_begining_of_line( char32_t ); 215 | Replxx::ACTION_RESULT go_to_end_of_line( char32_t ); 216 | Replxx::ACTION_RESULT move_one_char_left( char32_t ); 217 | Replxx::ACTION_RESULT move_one_char_right( char32_t ); 218 | template 219 | Replxx::ACTION_RESULT move_one_word_left( char32_t ); 220 | template 221 | Replxx::ACTION_RESULT move_one_word_right( char32_t ); 222 | template 223 | Replxx::ACTION_RESULT kill_word_to_left( char32_t ); 224 | template 225 | Replxx::ACTION_RESULT kill_word_to_right( char32_t ); 226 | Replxx::ACTION_RESULT kill_to_whitespace_to_left( char32_t ); 227 | Replxx::ACTION_RESULT kill_to_begining_of_line( char32_t ); 228 | Replxx::ACTION_RESULT kill_to_end_of_line( char32_t ); 229 | Replxx::ACTION_RESULT yank( char32_t ); 230 | Replxx::ACTION_RESULT yank_cycle( char32_t ); 231 | Replxx::ACTION_RESULT yank_last_arg( char32_t ); 232 | template 233 | Replxx::ACTION_RESULT capitalize_word( char32_t ); 234 | template 235 | Replxx::ACTION_RESULT lowercase_word( char32_t ); 236 | template 237 | Replxx::ACTION_RESULT uppercase_word( char32_t ); 238 | Replxx::ACTION_RESULT transpose_characters( char32_t ); 239 | Replxx::ACTION_RESULT abort_line( char32_t ); 240 | Replxx::ACTION_RESULT send_eof( char32_t ); 241 | Replxx::ACTION_RESULT delete_character( char32_t ); 242 | Replxx::ACTION_RESULT backspace_character( char32_t ); 243 | Replxx::ACTION_RESULT commit_line( char32_t ); 244 | Replxx::ACTION_RESULT line_next( char32_t ); 245 | Replxx::ACTION_RESULT line_previous( char32_t ); 246 | Replxx::ACTION_RESULT history_next( char32_t ); 247 | Replxx::ACTION_RESULT history_previous( char32_t ); 248 | Replxx::ACTION_RESULT history_move( bool ); 249 | Replxx::ACTION_RESULT history_first( char32_t ); 250 | Replxx::ACTION_RESULT history_last( char32_t ); 251 | Replxx::ACTION_RESULT history_restore( char32_t ); 252 | Replxx::ACTION_RESULT history_restore_current( char32_t ); 253 | Replxx::ACTION_RESULT history_jump( bool ); 254 | Replxx::ACTION_RESULT hint_next( char32_t ); 255 | Replxx::ACTION_RESULT hint_previous( char32_t ); 256 | Replxx::ACTION_RESULT hint_move( bool ); 257 | Replxx::ACTION_RESULT toggle_overwrite_mode( char32_t ); 258 | #ifndef _WIN32 259 | Replxx::ACTION_RESULT verbatim_insert( char32_t ); 260 | Replxx::ACTION_RESULT suspend( char32_t ); 261 | #endif 262 | Replxx::ACTION_RESULT complete_line( char32_t ); 263 | Replxx::ACTION_RESULT complete_next( char32_t ); 264 | Replxx::ACTION_RESULT complete_previous( char32_t ); 265 | Replxx::ACTION_RESULT complete( bool ); 266 | Replxx::ACTION_RESULT incremental_history_search( char32_t startChar ); 267 | Replxx::ACTION_RESULT common_prefix_search( char32_t startChar ); 268 | Replxx::ACTION_RESULT bracketed_paste( char32_t startChar ); 269 | char32_t read_char( HINT_ACTION = HINT_ACTION::SKIP ); 270 | char const* read_from_stdin( void ); 271 | char32_t do_complete_line( bool ); 272 | void call_modify_callback( void ); 273 | completions_t call_completer( std::string const& input, int& ) const; 274 | hints_t call_hinter( std::string const& input, int&, Replxx::Color& color ) const; 275 | void refresh_line( HINT_ACTION = HINT_ACTION::REGENERATE ); 276 | void move_cursor( void ); 277 | void indent( void ); 278 | int virtual_render( char32_t const*, int, int&, int&, Prompt const* = nullptr ); 279 | void render( char32_t ); 280 | void render( HINT_ACTION ); 281 | void handle_hints( HINT_ACTION ); 282 | void set_color( Replxx::Color ); 283 | int context_length( void ); 284 | int prev_newline_position( int ) const; 285 | int next_newline_position( int ) const; 286 | int pos_in_line( void ) const; 287 | void clear( void ); 288 | void repaint( void ); 289 | template 290 | bool is_word_break_character( char32_t ) const; 291 | void dynamic_refresh(Prompt& oldPrompt, Prompt& newPrompt, char32_t* buf32, int len, int pos); 292 | char const* finalize_input( char const* ); 293 | void clear_self_to_end_of_screen( Prompt const* = nullptr ); 294 | typedef struct { 295 | int index; 296 | bool error; 297 | } paren_info_t; 298 | paren_info_t matching_paren( void ); 299 | }; 300 | 301 | } 302 | 303 | #endif 304 | 305 | -------------------------------------------------------------------------------- /src/wcwidth.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an implementation of wcwidth() and wcswidth() (defined in 3 | * IEEE Std 1002.1-2001) for Unicode. 4 | * 5 | * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html 6 | * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html 7 | * 8 | * In fixed-width output devices, Latin characters all occupy a single 9 | * "cell" position of equal width, whereas ideographic CJK characters 10 | * occupy two such cells. Interoperability between terminal-line 11 | * applications and (teletype-style) character terminals using the 12 | * UTF-8 encoding requires agreement on which character should advance 13 | * the cursor by how many cell positions. No established formal 14 | * standards exist at present on which Unicode character shall occupy 15 | * how many cell positions on character terminals. These routines are 16 | * a first attempt of defining such behavior based on simple rules 17 | * applied to data provided by the Unicode Consortium. 18 | * 19 | * For some graphical characters, the Unicode standard explicitly 20 | * defines a character-cell width via the definition of the East Asian 21 | * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. 22 | * In all these cases, there is no ambiguity about which width a 23 | * terminal shall use. For characters in the East Asian Ambiguous (A) 24 | * class, the width choice depends purely on a preference of backward 25 | * compatibility with either historic CJK or Western practice. 26 | * Choosing single-width for these characters is easy to justify as 27 | * the appropriate long-term solution, as the CJK practice of 28 | * displaying these characters as double-width comes from historic 29 | * implementation simplicity (8-bit encoded characters were displayed 30 | * single-width and 16-bit ones double-width, even for Greek, 31 | * Cyrillic, etc.) and not any typographic considerations. 32 | * 33 | * Much less clear is the choice of width for the Not East Asian 34 | * (Neutral) class. Existing practice does not dictate a width for any 35 | * of these characters. It would nevertheless make sense 36 | * typographically to allocate two character cells to characters such 37 | * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be 38 | * represented adequately with a single-width glyph. The following 39 | * routines at present merely assign a single-cell width to all 40 | * neutral characters, in the interest of simplicity. This is not 41 | * entirely satisfactory and should be reconsidered before 42 | * establishing a formal standard in this area. At the moment, the 43 | * decision which Not East Asian (Neutral) characters should be 44 | * represented by double-width glyphs cannot yet be answered by 45 | * applying a simple rule from the Unicode database content. Setting 46 | * up a proper standard for the behavior of UTF-8 character terminals 47 | * will require a careful analysis not only of each Unicode character, 48 | * but also of each presentation form, something the author of these 49 | * routines has avoided to do so far. 50 | * 51 | * http://www.unicode.org/unicode/reports/tr11/ 52 | * 53 | * Markus Kuhn -- 2007-05-26 (Unicode 5.0) 54 | * 55 | * Permission to use, copy, modify, and distribute this software 56 | * for any purpose and without fee is hereby granted. The author 57 | * disclaims all warranties with regard to this software. 58 | * 59 | * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c 60 | */ 61 | 62 | #include 63 | #include 64 | #include 65 | 66 | namespace replxx { 67 | 68 | struct interval { 69 | char32_t first; 70 | char32_t last; 71 | }; 72 | 73 | /* auxiliary function for binary search in interval table */ 74 | static int bisearch(char32_t ucs, const struct interval *table, int max) { 75 | int min = 0; 76 | int mid; 77 | 78 | if (ucs < table[0].first || ucs > table[max].last) 79 | return 0; 80 | while (max >= min) { 81 | mid = (min + max) / 2; 82 | if (ucs > table[mid].last) 83 | min = mid + 1; 84 | else if (ucs < table[mid].first) 85 | max = mid - 1; 86 | else 87 | return 1; 88 | } 89 | 90 | return 0; 91 | } 92 | 93 | 94 | /* The following two functions define the column width of an ISO 10646 95 | * character as follows: 96 | * 97 | * - The null character (U+0000) has a column width of 0. 98 | * 99 | * - Other C0/C1 control characters and DEL will lead to a return 100 | * value of -1. 101 | * 102 | * - Non-spacing and enclosing combining characters (general 103 | * category code Mn or Me in the Unicode database) have a 104 | * column width of 0. 105 | * 106 | * - SOFT HYPHEN (U+00AD) has a column width of 1. 107 | * 108 | * - Other format characters (general category code Cf in the Unicode 109 | * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. 110 | * 111 | * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) 112 | * have a column width of 0. 113 | * 114 | * - Spacing characters in the East Asian Wide (W) or East Asian 115 | * Full-width (F) category as defined in Unicode Technical 116 | * Report #11 have a column width of 2. 117 | * 118 | * - All remaining characters (including all printable 119 | * ISO 8859-1 and WGL4 characters, Unicode control characters, 120 | * etc.) have a column width of 1. 121 | * 122 | * This implementation assumes that wchar_t characters are encoded 123 | * in ISO 10646. 124 | */ 125 | 126 | int mk_is_wide_char(char32_t ucs) { 127 | static const struct interval wide[] = { 128 | {0x1100, 0x115f}, {0x231a, 0x231b}, {0x2329, 0x232a}, 129 | {0x23e9, 0x23ec}, {0x23f0, 0x23f0}, {0x23f3, 0x23f3}, 130 | {0x25fd, 0x25fe}, {0x2614, 0x2615}, {0x2648, 0x2653}, 131 | {0x267f, 0x267f}, {0x2693, 0x2693}, {0x26a1, 0x26a1}, 132 | {0x26aa, 0x26ab}, {0x26bd, 0x26be}, {0x26c4, 0x26c5}, 133 | {0x26ce, 0x26ce}, {0x26d4, 0x26d4}, {0x26ea, 0x26ea}, 134 | {0x26f2, 0x26f3}, {0x26f5, 0x26f5}, {0x26fa, 0x26fa}, 135 | {0x26fd, 0x26fd}, {0x2705, 0x2705}, {0x270a, 0x270b}, 136 | {0x2728, 0x2728}, {0x274c, 0x274c}, {0x274e, 0x274e}, 137 | {0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797}, 138 | {0x27b0, 0x27b0}, {0x27bf, 0x27bf}, {0x2b1b, 0x2b1c}, 139 | {0x2b50, 0x2b50}, {0x2b55, 0x2b55}, {0x2e80, 0x2fdf}, 140 | {0x2ff0, 0x303e}, {0x3040, 0x3247}, {0x3250, 0x4dbf}, 141 | {0x4e00, 0xa4cf}, {0xa960, 0xa97f}, {0xac00, 0xd7a3}, 142 | {0xf900, 0xfaff}, {0xfe10, 0xfe19}, {0xfe30, 0xfe6f}, 143 | {0xff01, 0xff60}, {0xffe0, 0xffe6}, {0x16fe0, 0x16fe1}, 144 | {0x17000, 0x18aff}, {0x1b000, 0x1b12f}, {0x1b170, 0x1b2ff}, 145 | {0x1f004, 0x1f004}, {0x1f0cf, 0x1f0cf}, {0x1f18e, 0x1f18e}, 146 | {0x1f191, 0x1f19a}, {0x1f200, 0x1f202}, {0x1f210, 0x1f23b}, 147 | {0x1f240, 0x1f248}, {0x1f250, 0x1f251}, {0x1f260, 0x1f265}, 148 | {0x1f300, 0x1f320}, {0x1f32d, 0x1f335}, {0x1f337, 0x1f37c}, 149 | {0x1f37e, 0x1f393}, {0x1f3a0, 0x1f3ca}, {0x1f3cf, 0x1f3d3}, 150 | {0x1f3e0, 0x1f3f0}, {0x1f3f4, 0x1f3f4}, {0x1f3f8, 0x1f43e}, 151 | {0x1f440, 0x1f440}, {0x1f442, 0x1f4fc}, {0x1f4ff, 0x1f53d}, 152 | {0x1f54b, 0x1f54e}, {0x1f550, 0x1f567}, {0x1f57a, 0x1f57a}, 153 | {0x1f595, 0x1f596}, {0x1f5a4, 0x1f5a4}, {0x1f5fb, 0x1f64f}, 154 | {0x1f680, 0x1f6c5}, {0x1f6cc, 0x1f6cc}, {0x1f6d0, 0x1f6d2}, 155 | {0x1f6eb, 0x1f6ec}, {0x1f6f4, 0x1f6f8}, {0x1f910, 0x1f93e}, 156 | {0x1f940, 0x1f94c}, {0x1f950, 0x1f96b}, {0x1f980, 0x1f997}, 157 | {0x1f9c0, 0x1f9c0}, {0x1f9d0, 0x1f9e6}, {0x20000, 0x2fffd}, 158 | {0x30000, 0x3fffd}, 159 | }; 160 | 161 | if ( bisearch(ucs, wide, sizeof(wide) / sizeof(struct interval) - 1) ) { 162 | return 1; 163 | } 164 | 165 | return 0; 166 | } 167 | 168 | int mk_wcwidth(char32_t ucs) { 169 | /* sorted list of non-overlapping intervals of non-spacing characters */ 170 | /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ 171 | static const struct interval combining[] = { 172 | {0x00ad, 0x00ad}, {0x0300, 0x036f}, {0x0483, 0x0489}, 173 | {0x0591, 0x05bd}, {0x05bf, 0x05bf}, {0x05c1, 0x05c2}, 174 | {0x05c4, 0x05c5}, {0x05c7, 0x05c7}, {0x0610, 0x061a}, 175 | {0x061c, 0x061c}, {0x064b, 0x065f}, {0x0670, 0x0670}, 176 | {0x06d6, 0x06dc}, {0x06df, 0x06e4}, {0x06e7, 0x06e8}, 177 | {0x06ea, 0x06ed}, {0x0711, 0x0711}, {0x0730, 0x074a}, 178 | {0x07a6, 0x07b0}, {0x07eb, 0x07f3}, {0x0816, 0x0819}, 179 | {0x081b, 0x0823}, {0x0825, 0x0827}, {0x0829, 0x082d}, 180 | {0x0859, 0x085b}, {0x08d4, 0x08e1}, {0x08e3, 0x0902}, 181 | {0x093a, 0x093a}, {0x093c, 0x093c}, {0x0941, 0x0948}, 182 | {0x094d, 0x094d}, {0x0951, 0x0957}, {0x0962, 0x0963}, 183 | {0x0981, 0x0981}, {0x09bc, 0x09bc}, {0x09c1, 0x09c4}, 184 | {0x09cd, 0x09cd}, {0x09e2, 0x09e3}, {0x0a01, 0x0a02}, 185 | {0x0a3c, 0x0a3c}, {0x0a41, 0x0a42}, {0x0a47, 0x0a48}, 186 | {0x0a4b, 0x0a4d}, {0x0a51, 0x0a51}, {0x0a70, 0x0a71}, 187 | {0x0a75, 0x0a75}, {0x0a81, 0x0a82}, {0x0abc, 0x0abc}, 188 | {0x0ac1, 0x0ac5}, {0x0ac7, 0x0ac8}, {0x0acd, 0x0acd}, 189 | {0x0ae2, 0x0ae3}, {0x0afa, 0x0aff}, {0x0b01, 0x0b01}, 190 | {0x0b3c, 0x0b3c}, {0x0b3f, 0x0b3f}, {0x0b41, 0x0b44}, 191 | {0x0b4d, 0x0b4d}, {0x0b56, 0x0b56}, {0x0b62, 0x0b63}, 192 | {0x0b82, 0x0b82}, {0x0bc0, 0x0bc0}, {0x0bcd, 0x0bcd}, 193 | {0x0c00, 0x0c00}, {0x0c3e, 0x0c40}, {0x0c46, 0x0c48}, 194 | {0x0c4a, 0x0c4d}, {0x0c55, 0x0c56}, {0x0c62, 0x0c63}, 195 | {0x0c81, 0x0c81}, {0x0cbc, 0x0cbc}, {0x0cbf, 0x0cbf}, 196 | {0x0cc6, 0x0cc6}, {0x0ccc, 0x0ccd}, {0x0ce2, 0x0ce3}, 197 | {0x0d00, 0x0d01}, {0x0d3b, 0x0d3c}, {0x0d41, 0x0d44}, 198 | {0x0d4d, 0x0d4d}, {0x0d62, 0x0d63}, {0x0dca, 0x0dca}, 199 | {0x0dd2, 0x0dd4}, {0x0dd6, 0x0dd6}, {0x0e31, 0x0e31}, 200 | {0x0e34, 0x0e3a}, {0x0e47, 0x0e4e}, {0x0eb1, 0x0eb1}, 201 | {0x0eb4, 0x0eb9}, {0x0ebb, 0x0ebc}, {0x0ec8, 0x0ecd}, 202 | {0x0f18, 0x0f19}, {0x0f35, 0x0f35}, {0x0f37, 0x0f37}, 203 | {0x0f39, 0x0f39}, {0x0f71, 0x0f7e}, {0x0f80, 0x0f84}, 204 | {0x0f86, 0x0f87}, {0x0f8d, 0x0f97}, {0x0f99, 0x0fbc}, 205 | {0x0fc6, 0x0fc6}, {0x102d, 0x1030}, {0x1032, 0x1037}, 206 | {0x1039, 0x103a}, {0x103d, 0x103e}, {0x1058, 0x1059}, 207 | {0x105e, 0x1060}, {0x1071, 0x1074}, {0x1082, 0x1082}, 208 | {0x1085, 0x1086}, {0x108d, 0x108d}, {0x109d, 0x109d}, 209 | {0x1160, 0x11ff}, {0x135d, 0x135f}, {0x1712, 0x1714}, 210 | {0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773}, 211 | {0x17b4, 0x17b5}, {0x17b7, 0x17bd}, {0x17c6, 0x17c6}, 212 | {0x17c9, 0x17d3}, {0x17dd, 0x17dd}, {0x180b, 0x180e}, 213 | {0x1885, 0x1886}, {0x18a9, 0x18a9}, {0x1920, 0x1922}, 214 | {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193b}, 215 | {0x1a17, 0x1a18}, {0x1a1b, 0x1a1b}, {0x1a56, 0x1a56}, 216 | {0x1a58, 0x1a5e}, {0x1a60, 0x1a60}, {0x1a62, 0x1a62}, 217 | {0x1a65, 0x1a6c}, {0x1a73, 0x1a7c}, {0x1a7f, 0x1a7f}, 218 | {0x1ab0, 0x1abe}, {0x1b00, 0x1b03}, {0x1b34, 0x1b34}, 219 | {0x1b36, 0x1b3a}, {0x1b3c, 0x1b3c}, {0x1b42, 0x1b42}, 220 | {0x1b6b, 0x1b73}, {0x1b80, 0x1b81}, {0x1ba2, 0x1ba5}, 221 | {0x1ba8, 0x1ba9}, {0x1bab, 0x1bad}, {0x1be6, 0x1be6}, 222 | {0x1be8, 0x1be9}, {0x1bed, 0x1bed}, {0x1bef, 0x1bf1}, 223 | {0x1c2c, 0x1c33}, {0x1c36, 0x1c37}, {0x1cd0, 0x1cd2}, 224 | {0x1cd4, 0x1ce0}, {0x1ce2, 0x1ce8}, {0x1ced, 0x1ced}, 225 | {0x1cf4, 0x1cf4}, {0x1cf8, 0x1cf9}, {0x1dc0, 0x1df9}, 226 | {0x1dfb, 0x1dff}, {0x200b, 0x200f}, {0x202a, 0x202e}, 227 | {0x2060, 0x2064}, {0x2066, 0x206f}, {0x20d0, 0x20f0}, 228 | {0x2cef, 0x2cf1}, {0x2d7f, 0x2d7f}, {0x2de0, 0x2dff}, 229 | {0x302a, 0x302d}, {0x3099, 0x309a}, {0xa66f, 0xa672}, 230 | {0xa674, 0xa67d}, {0xa69e, 0xa69f}, {0xa6f0, 0xa6f1}, 231 | {0xa802, 0xa802}, {0xa806, 0xa806}, {0xa80b, 0xa80b}, 232 | {0xa825, 0xa826}, {0xa8c4, 0xa8c5}, {0xa8e0, 0xa8f1}, 233 | {0xa926, 0xa92d}, {0xa947, 0xa951}, {0xa980, 0xa982}, 234 | {0xa9b3, 0xa9b3}, {0xa9b6, 0xa9b9}, {0xa9bc, 0xa9bc}, 235 | {0xa9e5, 0xa9e5}, {0xaa29, 0xaa2e}, {0xaa31, 0xaa32}, 236 | {0xaa35, 0xaa36}, {0xaa43, 0xaa43}, {0xaa4c, 0xaa4c}, 237 | {0xaa7c, 0xaa7c}, {0xaab0, 0xaab0}, {0xaab2, 0xaab4}, 238 | {0xaab7, 0xaab8}, {0xaabe, 0xaabf}, {0xaac1, 0xaac1}, 239 | {0xaaec, 0xaaed}, {0xaaf6, 0xaaf6}, {0xabe5, 0xabe5}, 240 | {0xabe8, 0xabe8}, {0xabed, 0xabed}, {0xfb1e, 0xfb1e}, 241 | {0xfe00, 0xfe0f}, {0xfe20, 0xfe2f}, {0xfeff, 0xfeff}, 242 | {0xfff9, 0xfffb}, {0x101fd, 0x101fd}, {0x102e0, 0x102e0}, 243 | {0x10376, 0x1037a}, {0x10a01, 0x10a03}, {0x10a05, 0x10a06}, 244 | {0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f}, 245 | {0x10ae5, 0x10ae6}, {0x11001, 0x11001}, {0x11038, 0x11046}, 246 | {0x1107f, 0x11081}, {0x110b3, 0x110b6}, {0x110b9, 0x110ba}, 247 | {0x11100, 0x11102}, {0x11127, 0x1112b}, {0x1112d, 0x11134}, 248 | {0x11173, 0x11173}, {0x11180, 0x11181}, {0x111b6, 0x111be}, 249 | {0x111ca, 0x111cc}, {0x1122f, 0x11231}, {0x11234, 0x11234}, 250 | {0x11236, 0x11237}, {0x1123e, 0x1123e}, {0x112df, 0x112df}, 251 | {0x112e3, 0x112ea}, {0x11300, 0x11301}, {0x1133c, 0x1133c}, 252 | {0x11340, 0x11340}, {0x11366, 0x1136c}, {0x11370, 0x11374}, 253 | {0x11438, 0x1143f}, {0x11442, 0x11444}, {0x11446, 0x11446}, 254 | {0x114b3, 0x114b8}, {0x114ba, 0x114ba}, {0x114bf, 0x114c0}, 255 | {0x114c2, 0x114c3}, {0x115b2, 0x115b5}, {0x115bc, 0x115bd}, 256 | {0x115bf, 0x115c0}, {0x115dc, 0x115dd}, {0x11633, 0x1163a}, 257 | {0x1163d, 0x1163d}, {0x1163f, 0x11640}, {0x116ab, 0x116ab}, 258 | {0x116ad, 0x116ad}, {0x116b0, 0x116b5}, {0x116b7, 0x116b7}, 259 | {0x1171d, 0x1171f}, {0x11722, 0x11725}, {0x11727, 0x1172b}, 260 | {0x11a01, 0x11a06}, {0x11a09, 0x11a0a}, {0x11a33, 0x11a38}, 261 | {0x11a3b, 0x11a3e}, {0x11a47, 0x11a47}, {0x11a51, 0x11a56}, 262 | {0x11a59, 0x11a5b}, {0x11a8a, 0x11a96}, {0x11a98, 0x11a99}, 263 | {0x11c30, 0x11c36}, {0x11c38, 0x11c3d}, {0x11c3f, 0x11c3f}, 264 | {0x11c92, 0x11ca7}, {0x11caa, 0x11cb0}, {0x11cb2, 0x11cb3}, 265 | {0x11cb5, 0x11cb6}, {0x11d31, 0x11d36}, {0x11d3a, 0x11d3a}, 266 | {0x11d3c, 0x11d3d}, {0x11d3f, 0x11d45}, {0x11d47, 0x11d47}, 267 | {0x16af0, 0x16af4}, {0x16b30, 0x16b36}, {0x16f8f, 0x16f92}, 268 | {0x1bc9d, 0x1bc9e}, {0x1bca0, 0x1bca3}, {0x1d167, 0x1d169}, 269 | {0x1d173, 0x1d182}, {0x1d185, 0x1d18b}, {0x1d1aa, 0x1d1ad}, 270 | {0x1d242, 0x1d244}, {0x1da00, 0x1da36}, {0x1da3b, 0x1da6c}, 271 | {0x1da75, 0x1da75}, {0x1da84, 0x1da84}, {0x1da9b, 0x1da9f}, 272 | {0x1daa1, 0x1daaf}, {0x1e000, 0x1e006}, {0x1e008, 0x1e018}, 273 | {0x1e01b, 0x1e021}, {0x1e023, 0x1e024}, {0x1e026, 0x1e02a}, 274 | {0x1e8d0, 0x1e8d6}, {0x1e944, 0x1e94a}, {0xe0001, 0xe0001}, 275 | {0xe0020, 0xe007f}, {0xe0100, 0xe01ef}, 276 | }; 277 | 278 | /* test for 8-bit control characters */ 279 | if ( ucs == 0 ) { 280 | return 0; 281 | } 282 | if ( ( ucs < 32 ) || ( ( ucs >= 0x7f ) && ( ucs < 0xa0 ) ) ) { 283 | return -1; 284 | } 285 | 286 | /* binary search in table of non-spacing characters */ 287 | if ( bisearch( ucs, combining, sizeof( combining ) / sizeof( struct interval ) - 1 ) ) { 288 | return 0; 289 | } 290 | 291 | /* if we arrive here, ucs is not a combining or C0/C1 control character */ 292 | return ( mk_is_wide_char( ucs ) ? 2 : 1 ); 293 | } 294 | 295 | } 296 | 297 | -------------------------------------------------------------------------------- /src/terminal.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef _WIN32 9 | 10 | #include 11 | #include 12 | #include 13 | #define isatty _isatty 14 | #define strcasecmp _stricmp 15 | #define strdup _strdup 16 | #define write _write 17 | #define STDIN_FILENO 0 18 | 19 | #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING 20 | static DWORD const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; 21 | #endif 22 | 23 | #include "windows.hxx" 24 | 25 | #else /* _WIN32 */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #endif /* _WIN32 */ 34 | 35 | #include "terminal.hxx" 36 | #include "conversion.hxx" 37 | #include "escape.hxx" 38 | #include "replxx.hxx" 39 | #include "util.hxx" 40 | 41 | using namespace std; 42 | 43 | namespace replxx { 44 | 45 | namespace tty { 46 | 47 | bool is_a_tty( int fd_ ) { 48 | bool aTTY( isatty( fd_ ) != 0 ); 49 | #ifdef _WIN32 50 | do { 51 | if ( aTTY ) { 52 | break; 53 | } 54 | HANDLE h( (HANDLE)_get_osfhandle( fd_ ) ); 55 | if ( h == INVALID_HANDLE_VALUE ) { 56 | break; 57 | } 58 | DWORD st( 0 ); 59 | if ( ! GetConsoleMode( h, &st ) ) { 60 | break; 61 | } 62 | aTTY = true; 63 | } while ( false ); 64 | #endif 65 | return ( aTTY ); 66 | } 67 | 68 | bool in( is_a_tty( 0 ) ); 69 | bool out( is_a_tty( 1 ) ); 70 | 71 | } 72 | 73 | #ifndef _WIN32 74 | Terminal* _terminal_ = nullptr; 75 | static void WindowSizeChanged( int ) { 76 | if ( ! _terminal_ ) { 77 | return; 78 | } 79 | _terminal_->notify_event( Terminal::EVENT_TYPE::RESIZE ); 80 | } 81 | #endif 82 | 83 | 84 | Terminal::Terminal( void ) 85 | #ifdef _WIN32 86 | : _consoleOut( INVALID_HANDLE_VALUE ) 87 | , _consoleIn( INVALID_HANDLE_VALUE ) 88 | , _origOutMode() 89 | , _origInMode() 90 | , _oldDisplayAttribute() 91 | , _inputCodePage( GetConsoleCP() ) 92 | , _outputCodePage( GetConsoleOutputCP() ) 93 | , _interrupt( INVALID_HANDLE_VALUE ) 94 | , _events() 95 | , _empty() 96 | #else 97 | : _origTermios() 98 | , _rawModeTermios() 99 | , _interrupt() 100 | #endif 101 | , _rawMode( false ) 102 | , _utf8() { 103 | #ifdef _WIN32 104 | _interrupt = CreateEvent( nullptr, true, false, TEXT( "replxx_interrupt_event" ) ); 105 | #else 106 | static_cast( ::pipe( _interrupt ) == 0 ); 107 | #endif 108 | } 109 | 110 | Terminal::~Terminal( void ) { 111 | if ( _rawMode ) { 112 | disable_raw_mode(); 113 | } 114 | #ifdef _WIN32 115 | CloseHandle( _interrupt ); 116 | #else 117 | static_cast( ::close( _interrupt[0] ) == 0 ); 118 | static_cast( ::close( _interrupt[1] ) == 0 ); 119 | #endif 120 | } 121 | 122 | void Terminal::write32( char32_t const* text32, int len32 ) { 123 | _utf8.assign( text32, len32 ); 124 | write8( _utf8.get(), _utf8.size() ); 125 | return; 126 | } 127 | 128 | void Terminal::write8( char const* data_, int size_ ) { 129 | #ifdef _WIN32 130 | bool temporarilyEnabled( false ); 131 | if ( _consoleOut == INVALID_HANDLE_VALUE ) { 132 | enable_out(); 133 | temporarilyEnabled = true; 134 | } 135 | int nWritten( win_write( _consoleOut, _autoEscape, data_, size_ ) ); 136 | if ( temporarilyEnabled ) { 137 | disable_out(); 138 | } 139 | #else 140 | int nWritten( write( 1, data_, size_ ) ); 141 | #endif 142 | if ( nWritten != size_ ) { 143 | throw std::runtime_error( "write failed" ); 144 | } 145 | return; 146 | } 147 | 148 | int Terminal::get_screen_columns( void ) { 149 | int cols( 0 ); 150 | #ifdef _WIN32 151 | CONSOLE_SCREEN_BUFFER_INFO inf; 152 | GetConsoleScreenBufferInfo( _consoleOut, &inf ); 153 | cols = inf.dwSize.X; 154 | #else 155 | struct winsize ws; 156 | cols = ( ioctl( 1, TIOCGWINSZ, &ws ) == -1 ) ? 80 : ws.ws_col; 157 | #endif 158 | // cols is 0 in certain circumstances like inside debugger, which creates 159 | // further issues 160 | return ( cols > 0 ) ? cols : 80; 161 | } 162 | 163 | int Terminal::get_screen_rows( void ) { 164 | int rows; 165 | #ifdef _WIN32 166 | CONSOLE_SCREEN_BUFFER_INFO inf; 167 | GetConsoleScreenBufferInfo( _consoleOut, &inf ); 168 | rows = 1 + inf.srWindow.Bottom - inf.srWindow.Top; 169 | #else 170 | struct winsize ws; 171 | rows = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 24 : ws.ws_row; 172 | #endif 173 | return (rows > 0) ? rows : 24; 174 | } 175 | 176 | namespace { 177 | inline int notty( void ) { 178 | errno = ENOTTY; 179 | return ( -1 ); 180 | } 181 | } 182 | 183 | void Terminal::enable_out( void ) { 184 | #ifdef _WIN32 185 | SetConsoleOutputCP( 65001 ); 186 | _consoleOut = GetStdHandle( STD_OUTPUT_HANDLE ); 187 | GetConsoleMode( _consoleOut, &_origOutMode ); 188 | _autoEscape = SetConsoleMode( _consoleOut, _origOutMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING ) != 0; 189 | #endif 190 | } 191 | 192 | void Terminal::disable_out( void ) { 193 | #ifdef _WIN32 194 | SetConsoleMode( _consoleOut, _origOutMode ); 195 | SetConsoleOutputCP( _outputCodePage ); 196 | _consoleOut = INVALID_HANDLE_VALUE; 197 | _autoEscape = false; 198 | #endif 199 | } 200 | 201 | void Terminal::enable_bracketed_paste( void ) { 202 | static char const BRACK_PASTE_INIT[] = "\033[?2004h"; 203 | write8( BRACK_PASTE_INIT, sizeof ( BRACK_PASTE_INIT ) - 1 ); 204 | } 205 | 206 | void Terminal::disable_bracketed_paste( void ) { 207 | static char const BRACK_PASTE_DISABLE[] = "\033[?2004l"; 208 | write8( BRACK_PASTE_DISABLE, sizeof ( BRACK_PASTE_DISABLE ) - 1 ); 209 | } 210 | 211 | int Terminal::enable_raw_mode( void ) { 212 | if ( _rawMode ) { 213 | return ( 0 ); 214 | } 215 | #ifdef _WIN32 216 | _consoleIn = GetStdHandle( STD_INPUT_HANDLE ); 217 | GetConsoleMode( _consoleIn, &_origInMode ); 218 | #else 219 | 220 | if ( ! tty::in ) { 221 | return ( notty() ); 222 | } 223 | if ( tcgetattr( 0, &_origTermios ) == -1 ) { 224 | return ( notty() ); 225 | } 226 | 227 | _rawModeTermios = _origTermios; /* modify the original mode */ 228 | /* input modes: no break, no CR to NL, no parity check, no strip char, 229 | * no start/stop output control. */ 230 | _rawModeTermios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 231 | /* output modes - disable post processing */ 232 | // this is wrong, we don't want _rawModeTermios output, it turns newlines into straight 233 | // linefeeds 234 | // _rawModeTermios.c_oflag &= ~(OPOST); 235 | /* control modes - set 8 bit chars */ 236 | _rawModeTermios.c_cflag |= (CS8); 237 | /* local modes - echoing off, canonical off, no extended functions, 238 | * no signal chars (^Z,^C) */ 239 | _rawModeTermios.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 240 | /* control chars - set return condition: min number of bytes and timer. 241 | * We want read to return every single byte, without timeout. */ 242 | _rawModeTermios.c_cc[VMIN] = 1; 243 | _rawModeTermios.c_cc[VTIME] = 0; /* 1 byte, no timer */ 244 | 245 | #endif 246 | 247 | _rawMode = true; 248 | if ( reset_raw_mode() < 0 ) { 249 | _rawMode = false; 250 | return ( notty() ); 251 | } 252 | 253 | #ifndef _WIN32 254 | _terminal_ = this; 255 | #endif 256 | return ( 0 ); 257 | } 258 | 259 | int Terminal::reset_raw_mode( void ) { 260 | if ( ! _rawMode ) { 261 | return ( -1 ); 262 | } 263 | #ifdef _WIN32 264 | SetConsoleMode( 265 | _consoleIn, 266 | ( _origInMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT ) ) | ENABLE_QUICK_EDIT_MODE 267 | ); 268 | SetConsoleCP( 65001 ); 269 | enable_out(); 270 | return ( 0 ); 271 | #else 272 | /* put terminal in raw mode after flushing */ 273 | return ( tcsetattr( 0, TCSADRAIN, &_rawModeTermios ) ); 274 | #endif 275 | } 276 | 277 | void Terminal::disable_raw_mode(void) { 278 | if ( ! _rawMode ) { 279 | return; 280 | } 281 | #ifdef _WIN32 282 | disable_out(); 283 | SetConsoleMode( _consoleIn, _origInMode ); 284 | SetConsoleCP( _inputCodePage ); 285 | _consoleIn = INVALID_HANDLE_VALUE; 286 | #else 287 | _terminal_ = nullptr; 288 | if ( tcsetattr( 0, TCSADRAIN, &_origTermios ) == -1 ) { 289 | return; 290 | } 291 | #endif 292 | _rawMode = false; 293 | return; 294 | } 295 | 296 | #ifndef _WIN32 297 | 298 | /** 299 | * Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode 300 | * (char32_t) character it encodes 301 | * 302 | * @return char32_t Unicode character 303 | */ 304 | char32_t read_unicode_character(void) { 305 | static char8_t utf8String[5]; 306 | static size_t utf8Count = 0; 307 | while (true) { 308 | char8_t c; 309 | 310 | /* Continue reading if interrupted by signal. */ 311 | ssize_t nread; 312 | do { 313 | nread = read( STDIN_FILENO, &c, 1 ); 314 | } while ((nread == -1) && (errno == EINTR)); 315 | 316 | if (nread <= 0) return 0; 317 | if (c <= 0x7F || locale::is8BitEncoding) { // short circuit ASCII 318 | utf8Count = 0; 319 | return c; 320 | } else if (utf8Count < sizeof(utf8String) - 1) { 321 | utf8String[utf8Count++] = c; 322 | utf8String[utf8Count] = 0; 323 | char32_t unicodeChar[2]; 324 | int ucharCount( 0 ); 325 | ConversionResult res = copyString8to32(unicodeChar, 2, ucharCount, utf8String); 326 | if (res == conversionOK && ucharCount) { 327 | utf8Count = 0; 328 | return unicodeChar[0]; 329 | } 330 | } else { 331 | utf8Count = 0; // this shouldn't happen: got four bytes but no UTF-8 character 332 | } 333 | } 334 | } 335 | 336 | #endif // #ifndef _WIN32 337 | 338 | void beep() { 339 | fprintf(stderr, "\x7"); // ctrl-G == bell/beep 340 | fflush(stderr); 341 | } 342 | 343 | // replxx_read_char -- read a keystroke or keychord from the keyboard, and translate it 344 | // into an encoded "keystroke". When convenient, extended keys are translated into their 345 | // simpler Emacs keystrokes, so an unmodified "left arrow" becomes Ctrl-B. 346 | // 347 | // A return value of zero means "no input available", and a return value of -1 348 | // means "invalid key". 349 | // 350 | char32_t Terminal::read_char( void ) { 351 | char32_t c( 0 ); 352 | #ifdef _WIN32 353 | INPUT_RECORD rec; 354 | DWORD count; 355 | char32_t modifierKeys = 0; 356 | bool escSeen = false; 357 | int highSurrogate( 0 ); 358 | while (true) { 359 | ReadConsoleInputW( _consoleIn, &rec, 1, &count ); 360 | #if __REPLXX_DEBUG__ // helper for debugging keystrokes, display info in the debug "Output" 361 | // window in the debugger 362 | { 363 | if ( rec.EventType == KEY_EVENT ) { 364 | //if ( rec.Event.KeyEvent.uChar.UnicodeChar ) { 365 | char buf[1024]; 366 | sprintf( 367 | buf, 368 | "Unicode character 0x%04X, repeat count %d, virtual keycode 0x%04X, " 369 | "virtual scancode 0x%04X, key %s%s%s%s%s\n", 370 | rec.Event.KeyEvent.uChar.UnicodeChar, 371 | rec.Event.KeyEvent.wRepeatCount, 372 | rec.Event.KeyEvent.wVirtualKeyCode, 373 | rec.Event.KeyEvent.wVirtualScanCode, 374 | rec.Event.KeyEvent.bKeyDown ? "down" : "up", 375 | (rec.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED) ? " L-Ctrl" : "", 376 | (rec.Event.KeyEvent.dwControlKeyState & RIGHT_CTRL_PRESSED) ? " R-Ctrl" : "", 377 | (rec.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) ? " L-Alt" : "", 378 | (rec.Event.KeyEvent.dwControlKeyState & RIGHT_ALT_PRESSED) ? " R-Alt" : "" 379 | ); 380 | OutputDebugStringA( buf ); 381 | //} 382 | } 383 | } 384 | #endif 385 | if ( rec.EventType != KEY_EVENT ) { 386 | continue; 387 | } 388 | // Windows provides for entry of characters that are not on your keyboard by sending the 389 | // Unicode characters as a "key up" with virtual keycode 0x12 (VK_MENU == Alt key) ... 390 | // accept these characters, otherwise only process characters on "key down" 391 | if ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) ) { 392 | continue; 393 | } 394 | modifierKeys = 0; 395 | // AltGr is encoded as ( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ), so don't treat this 396 | // combination as either CTRL or META we just turn off those two bits, so it is still 397 | // possible to combine CTRL and/or META with an AltGr key by using right-Ctrl and/or 398 | // left-Alt 399 | DWORD const AltGr( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ); 400 | if ( ( rec.Event.KeyEvent.dwControlKeyState & AltGr ) == AltGr ) { 401 | rec.Event.KeyEvent.dwControlKeyState &= ~( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ); 402 | } 403 | if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED ) ) { 404 | modifierKeys |= Replxx::KEY::BASE_CONTROL; 405 | } 406 | if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED ) ) { 407 | modifierKeys |= Replxx::KEY::BASE_META; 408 | } 409 | if ( escSeen ) { 410 | modifierKeys |= Replxx::KEY::BASE_META; 411 | } 412 | int key( rec.Event.KeyEvent.uChar.UnicodeChar ); 413 | if ( key == 0 ) { 414 | if ( rec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED ) { 415 | modifierKeys |= Replxx::KEY::BASE_SHIFT; 416 | } 417 | switch ( rec.Event.KeyEvent.wVirtualKeyCode ) { 418 | case VK_LEFT: 419 | return modifierKeys | Replxx::KEY::LEFT; 420 | case VK_RIGHT: 421 | return modifierKeys | Replxx::KEY::RIGHT; 422 | case VK_UP: 423 | return modifierKeys | Replxx::KEY::UP; 424 | case VK_DOWN: 425 | return modifierKeys | Replxx::KEY::DOWN; 426 | case VK_DELETE: 427 | return modifierKeys | Replxx::KEY::DELETE; 428 | case VK_HOME: 429 | return modifierKeys | Replxx::KEY::HOME; 430 | case VK_END: 431 | return modifierKeys | Replxx::KEY::END; 432 | case VK_PRIOR: 433 | return modifierKeys | Replxx::KEY::PAGE_UP; 434 | case VK_NEXT: 435 | return modifierKeys | Replxx::KEY::PAGE_DOWN; 436 | case VK_F1: 437 | return modifierKeys | Replxx::KEY::F1; 438 | case VK_F2: 439 | return modifierKeys | Replxx::KEY::F2; 440 | case VK_F3: 441 | return modifierKeys | Replxx::KEY::F3; 442 | case VK_F4: 443 | return modifierKeys | Replxx::KEY::F4; 444 | case VK_F5: 445 | return modifierKeys | Replxx::KEY::F5; 446 | case VK_F6: 447 | return modifierKeys | Replxx::KEY::F6; 448 | case VK_F7: 449 | return modifierKeys | Replxx::KEY::F7; 450 | case VK_F8: 451 | return modifierKeys | Replxx::KEY::F8; 452 | case VK_F9: 453 | return modifierKeys | Replxx::KEY::F9; 454 | case VK_F10: 455 | return modifierKeys | Replxx::KEY::F10; 456 | case VK_F11: 457 | return modifierKeys | Replxx::KEY::F11; 458 | case VK_F12: 459 | return modifierKeys | Replxx::KEY::F12; 460 | default: 461 | continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them 462 | } 463 | } else if ( key == Replxx::KEY::ESCAPE ) { // ESC, set flag for later 464 | escSeen = true; 465 | continue; 466 | } else if ( ( key >= 0xD800 ) && ( key <= 0xDBFF ) ) { 467 | highSurrogate = key - 0xD800; 468 | continue; 469 | } else { 470 | if ( ( key == 13 ) && ( rec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED ) ) { 471 | key = 10; 472 | } 473 | // we got a real character, return it 474 | if ( ( key >= 0xDC00 ) && ( key <= 0xDFFF ) ) { 475 | key -= 0xDC00; 476 | key |= ( highSurrogate << 10 ); 477 | key += 0x10000; 478 | } 479 | if ( is_control_code( key ) ) { 480 | key = control_to_human( key ); 481 | modifierKeys |= Replxx::KEY::BASE_CONTROL; 482 | } 483 | key |= modifierKeys; 484 | highSurrogate = 0; 485 | c = key; 486 | break; 487 | } 488 | } 489 | 490 | #else 491 | c = read_unicode_character(); 492 | if (c == 0) { 493 | return 0; 494 | } 495 | 496 | // If _DEBUG_LINUX_KEYBOARD is set, then ctrl-^ puts us into a keyboard 497 | // debugging mode 498 | // where we print out decimal and decoded values for whatever the "terminal" 499 | // program 500 | // gives us on different keystrokes. Hit ctrl-C to exit this mode. 501 | // 502 | #ifdef __REPLXX_DEBUG__ 503 | if (c == ctrlChar('^')) { // ctrl-^, special debug mode, prints all keys hit, 504 | // ctrl-C to get out 505 | printf( 506 | "\nEntering keyboard debugging mode (on ctrl-^), press ctrl-C to exit " 507 | "this mode\n"); 508 | while (true) { 509 | unsigned char keys[10]; 510 | int ret = read(0, keys, 10); 511 | 512 | if (ret <= 0) { 513 | printf("\nret: %d\n", ret); 514 | } 515 | for (int i = 0; i < ret; ++i) { 516 | char32_t key = static_cast(keys[i]); 517 | char* friendlyTextPtr; 518 | char friendlyTextBuf[10]; 519 | const char* prefixText = (key < 0x80) ? "" : "0x80+"; 520 | char32_t keyCopy = (key < 0x80) ? key : key - 0x80; 521 | if (keyCopy >= '!' && keyCopy <= '~') { // printable 522 | friendlyTextBuf[0] = '\''; 523 | friendlyTextBuf[1] = keyCopy; 524 | friendlyTextBuf[2] = '\''; 525 | friendlyTextBuf[3] = 0; 526 | friendlyTextPtr = friendlyTextBuf; 527 | } else if (keyCopy == ' ') { 528 | friendlyTextPtr = const_cast("space"); 529 | } else if (keyCopy == 27) { 530 | friendlyTextPtr = const_cast("ESC"); 531 | } else if (keyCopy == 0) { 532 | friendlyTextPtr = const_cast("NUL"); 533 | } else if (keyCopy == 127) { 534 | friendlyTextPtr = const_cast("DEL"); 535 | } else { 536 | friendlyTextBuf[0] = '^'; 537 | friendlyTextBuf[1] = control_to_human( keyCopy ); 538 | friendlyTextBuf[2] = 0; 539 | friendlyTextPtr = friendlyTextBuf; 540 | } 541 | printf("%d x%02X (%s%s) ", key, key, prefixText, friendlyTextPtr); 542 | } 543 | printf("\x1b[1G\n"); // go to first column of new line 544 | 545 | // drop out of this loop on ctrl-C 546 | if (keys[0] == ctrlChar('C')) { 547 | printf("Leaving keyboard debugging mode (on ctrl-C)\n"); 548 | fflush(stdout); 549 | return -2; 550 | } 551 | } 552 | } 553 | #endif // __REPLXX_DEBUG__ 554 | 555 | c = EscapeSequenceProcessing::doDispatch(c); 556 | if ( is_control_code( c ) ) { 557 | c = Replxx::KEY::control( control_to_human( c ) ); 558 | } 559 | #endif // #_WIN32 560 | return ( c ); 561 | } 562 | 563 | Terminal::EVENT_TYPE Terminal::wait_for_input( int long timeout_ ) { 564 | #ifdef _WIN32 565 | std::array handles = { _consoleIn, _interrupt }; 566 | while ( true ) { 567 | DWORD event( WaitForMultipleObjects( static_cast( handles.size() ), handles.data(), false, timeout_ > 0 ? timeout_ : INFINITE ) ); 568 | switch ( event ) { 569 | case ( WAIT_OBJECT_0 + 0 ): { 570 | // peek events that will be skipped 571 | INPUT_RECORD rec; 572 | DWORD count; 573 | PeekConsoleInputW( _consoleIn, &rec, 1, &count ); 574 | 575 | if ( 576 | ( rec.EventType != KEY_EVENT ) 577 | || ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) ) 578 | ) { 579 | // read the event to unsignal the handle 580 | ReadConsoleInputW( _consoleIn, &rec, 1, &count ); 581 | continue; 582 | } else if ( rec.EventType == KEY_EVENT ) { 583 | int key( rec.Event.KeyEvent.uChar.UnicodeChar ); 584 | if ( key == 0 ) { 585 | switch ( rec.Event.KeyEvent.wVirtualKeyCode ) { 586 | case VK_LEFT: 587 | case VK_RIGHT: 588 | case VK_UP: 589 | case VK_DOWN: 590 | case VK_DELETE: 591 | case VK_HOME: 592 | case VK_END: 593 | case VK_PRIOR: 594 | case VK_NEXT: 595 | case VK_F1: 596 | case VK_F2: 597 | case VK_F3: 598 | case VK_F4: 599 | case VK_F5: 600 | case VK_F6: 601 | case VK_F7: 602 | case VK_F8: 603 | case VK_F9: 604 | case VK_F10: 605 | case VK_F11: 606 | case VK_F12: 607 | break; 608 | default: 609 | ReadConsoleInputW( _consoleIn, &rec, 1, &count ); 610 | continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them 611 | } 612 | } 613 | } 614 | 615 | return ( EVENT_TYPE::KEY_PRESS ); 616 | } 617 | case ( WAIT_OBJECT_0 + 1 ): { 618 | ResetEvent( _interrupt ); 619 | if ( _events.empty() ) { 620 | continue; 621 | } 622 | EVENT_TYPE eventType( _events.front() ); 623 | _events.pop_front(); 624 | return ( eventType ); 625 | } 626 | case ( WAIT_TIMEOUT ): { 627 | return ( EVENT_TYPE::TIMEOUT ); 628 | } 629 | } 630 | } 631 | #else 632 | fd_set fdSet; 633 | int nfds( max( _interrupt[0], _interrupt[1] ) + 1 ); 634 | while ( true ) { 635 | FD_ZERO( &fdSet ); 636 | FD_SET( 0, &fdSet ); 637 | FD_SET( _interrupt[0], &fdSet ); 638 | timeval tv{ timeout_ / 1000, static_cast( ( timeout_ % 1000 ) * 1000 ) }; 639 | int err( select( nfds, &fdSet, nullptr, nullptr, timeout_ > 0 ? &tv : nullptr ) ); 640 | if ( ( err == -1 ) && ( errno == EINTR ) ) { 641 | continue; 642 | } 643 | if ( err == 0 ) { 644 | return ( EVENT_TYPE::TIMEOUT ); 645 | } 646 | if ( FD_ISSET( _interrupt[0], &fdSet ) ) { 647 | char data( 0 ); 648 | static_cast( read( _interrupt[0], &data, 1 ) == 1 ); 649 | if ( data == 'k' ) { 650 | return ( EVENT_TYPE::KEY_PRESS ); 651 | } 652 | if ( data == 'm' ) { 653 | return ( EVENT_TYPE::MESSAGE ); 654 | } 655 | if ( data == 'r' ) { 656 | return ( EVENT_TYPE::RESIZE ); 657 | } 658 | } 659 | if ( FD_ISSET( 0, &fdSet ) ) { 660 | return ( EVENT_TYPE::KEY_PRESS ); 661 | } 662 | } 663 | #endif 664 | } 665 | 666 | void Terminal::notify_event( EVENT_TYPE eventType_ ) { 667 | #ifdef _WIN32 668 | _events.push_back( eventType_ ); 669 | SetEvent( _interrupt ); 670 | #else 671 | char data( ( eventType_ == EVENT_TYPE::KEY_PRESS ) ? 'k' : ( eventType_ == EVENT_TYPE::MESSAGE ? 'm' : 'r' ) ); 672 | static_cast( write( _interrupt[1], &data, 1 ) == 1 ); 673 | #endif 674 | } 675 | 676 | /** 677 | * Clear the screen ONLY (no redisplay of anything) 678 | */ 679 | void Terminal::clear_screen( CLEAR_SCREEN clearScreen_ ) { 680 | #ifdef _WIN32 681 | if ( _autoEscape ) { 682 | #endif 683 | if ( clearScreen_ == CLEAR_SCREEN::WHOLE ) { 684 | char const clearCode[] = "\033c\033[H\033[2J\033[0m"; 685 | static_cast( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 ); 686 | } else { 687 | char const clearCode[] = "\033[J"; 688 | static_cast( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 ); 689 | } 690 | return; 691 | #ifdef _WIN32 692 | } 693 | COORD coord = { 0, 0 }; 694 | CONSOLE_SCREEN_BUFFER_INFO inf; 695 | HANDLE consoleOut( _consoleOut != INVALID_HANDLE_VALUE ? _consoleOut : GetStdHandle( STD_OUTPUT_HANDLE ) ); 696 | GetConsoleScreenBufferInfo( consoleOut, &inf ); 697 | if ( clearScreen_ == CLEAR_SCREEN::TO_END ) { 698 | coord = inf.dwCursorPosition; 699 | DWORD nWritten( 0 ); 700 | SHORT height( inf.srWindow.Bottom - inf.srWindow.Top ); 701 | DWORD yPos( inf.dwCursorPosition.Y - inf.srWindow.Top ); 702 | DWORD toWrite( ( height + 1 - yPos ) * inf.dwSize.X - inf.dwCursorPosition.X ); 703 | // FillConsoleOutputCharacterA( consoleOut, ' ', toWrite, coord, &nWritten ); 704 | _empty.resize( toWrite - 1, ' ' ); 705 | WriteConsoleA( consoleOut, _empty.data(), toWrite - 1, &nWritten, nullptr ); 706 | } else { 707 | COORD scrollTarget = { 0, static_cast( -inf.dwSize.Y ) }; 708 | CHAR_INFO fill{ TEXT( ' ' ), inf.wAttributes }; 709 | SMALL_RECT scrollRect = { 0, 0, inf.dwSize.X, inf.dwSize.Y }; 710 | ScrollConsoleScreenBuffer( consoleOut, &scrollRect, nullptr, scrollTarget, &fill ); 711 | } 712 | SetConsoleCursorPosition( consoleOut, coord ); 713 | #endif 714 | } 715 | 716 | void Terminal::jump_cursor( int xPos_, int yOffset_ ) { 717 | #ifdef _WIN32 718 | CONSOLE_SCREEN_BUFFER_INFO inf; 719 | GetConsoleScreenBufferInfo( _consoleOut, &inf ); 720 | inf.dwCursorPosition.X = xPos_; 721 | inf.dwCursorPosition.Y += yOffset_; 722 | SetConsoleCursorPosition( _consoleOut, inf.dwCursorPosition ); 723 | #else 724 | char seq[64]; 725 | if ( yOffset_ != 0 ) { // move the cursor up as required 726 | snprintf( seq, sizeof seq, "\033[%d%c", abs( yOffset_ ), yOffset_ > 0 ? 'B' : 'A' ); 727 | write8( seq, strlen( seq ) ); 728 | } 729 | // position at the end of the prompt, clear to end of screen 730 | snprintf( 731 | seq, sizeof seq, "\033[%dG", 732 | xPos_ + 1 /* 1-based on VT100 */ 733 | ); 734 | write8( seq, strlen( seq ) ); 735 | #endif 736 | } 737 | 738 | #ifdef _WIN32 739 | void Terminal::set_cursor_visible( bool visible_ ) { 740 | CONSOLE_CURSOR_INFO cursorInfo; 741 | GetConsoleCursorInfo( _consoleOut, &cursorInfo ); 742 | cursorInfo.bVisible = visible_; 743 | SetConsoleCursorInfo( _consoleOut, &cursorInfo ); 744 | return; 745 | } 746 | #else 747 | void Terminal::set_cursor_visible( bool ) {} 748 | #endif 749 | 750 | #ifndef _WIN32 751 | int Terminal::read_verbatim( char32_t* buffer_, int size_ ) { 752 | int len( 0 ); 753 | buffer_[len ++] = read_unicode_character(); 754 | int statusFlags( ::fcntl( STDIN_FILENO, F_GETFL, 0 ) ); 755 | ::fcntl( STDIN_FILENO, F_SETFL, statusFlags | O_NONBLOCK ); 756 | while ( len < size_ ) { 757 | char32_t c( read_unicode_character() ); 758 | if ( c == 0 ) { 759 | break; 760 | } 761 | buffer_[len ++] = c; 762 | } 763 | ::fcntl( STDIN_FILENO, F_SETFL, statusFlags ); 764 | return ( len ); 765 | } 766 | 767 | int Terminal::install_window_change_handler( void ) { 768 | struct sigaction sa; 769 | sigemptyset(&sa.sa_mask); 770 | sa.sa_flags = 0; 771 | sa.sa_handler = &WindowSizeChanged; 772 | 773 | if (sigaction(SIGWINCH, &sa, nullptr) == -1) { 774 | return errno; 775 | } 776 | return 0; 777 | } 778 | #endif 779 | 780 | } 781 | 782 | -------------------------------------------------------------------------------- /include/replxx.hxx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org) 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef HAVE_REPLXX_HXX_INCLUDED 32 | #define HAVE_REPLXX_HXX_INCLUDED 1 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | /* 41 | * For use in Windows DLLs: 42 | * 43 | * If you are building replxx into a DLL, 44 | * unless you are using supplied CMake based build, 45 | * ensure that 'REPLXX_BUILDING_DLL' is defined when 46 | * building the DLL so that proper symbols are exported. 47 | */ 48 | #if defined( _WIN32 ) && ! defined( REPLXX_STATIC ) 49 | # ifdef REPLXX_BUILDING_DLL 50 | # define REPLXX_IMPEXP __declspec( dllexport ) 51 | # else 52 | # define REPLXX_IMPEXP __declspec( dllimport ) 53 | # endif 54 | #else 55 | # define REPLXX_IMPEXP /**/ 56 | #endif 57 | 58 | #ifdef ERROR 59 | enum { ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 = ERROR }; 60 | #undef ERROR 61 | enum { ERROR = ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 }; 62 | #endif 63 | #ifdef ABORT 64 | enum { ABORT_8D12A2CA7E5A64036D7251A3EDA51A38 = ABORT }; 65 | #undef ABORT 66 | enum { ABORT = ABORT_8D12A2CA7E5A64036D7251A3EDA51A38 }; 67 | #endif 68 | #ifdef DELETE 69 | enum { DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E = DELETE }; 70 | #undef DELETE 71 | enum { DELETE = DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E }; 72 | #endif 73 | 74 | namespace replxx { 75 | 76 | class REPLXX_IMPEXP Replxx { 77 | public: 78 | enum class Color : int { 79 | BLACK = 0, 80 | RED = 1, 81 | GREEN = 2, 82 | BROWN = 3, 83 | BLUE = 4, 84 | MAGENTA = 5, 85 | CYAN = 6, 86 | LIGHTGRAY = 7, 87 | GRAY = 8, 88 | BRIGHTRED = 9, 89 | BRIGHTGREEN = 10, 90 | YELLOW = 11, 91 | BRIGHTBLUE = 12, 92 | BRIGHTMAGENTA = 13, 93 | BRIGHTCYAN = 14, 94 | WHITE = 15, 95 | DEFAULT = 1u << 16u 96 | }; 97 | struct KEY { 98 | static char32_t const BASE = 0x0010ffff + 1; 99 | static char32_t const BASE_SHIFT = 0x01000000; 100 | static char32_t const BASE_CONTROL = 0x02000000; 101 | static char32_t const BASE_META = 0x04000000; 102 | static char32_t const ESCAPE = 27; 103 | static char32_t const PAGE_UP = BASE + 1; 104 | static char32_t const PAGE_DOWN = PAGE_UP + 1; 105 | static char32_t const DOWN = PAGE_DOWN + 1; 106 | static char32_t const UP = DOWN + 1; 107 | static char32_t const LEFT = UP + 1; 108 | static char32_t const RIGHT = LEFT + 1; 109 | static char32_t const HOME = RIGHT + 1; 110 | static char32_t const END = HOME + 1; 111 | static char32_t const DELETE = END + 1; 112 | static char32_t const INSERT = DELETE + 1; 113 | static char32_t const F1 = INSERT + 1; 114 | static char32_t const F2 = F1 + 1; 115 | static char32_t const F3 = F2 + 1; 116 | static char32_t const F4 = F3 + 1; 117 | static char32_t const F5 = F4 + 1; 118 | static char32_t const F6 = F5 + 1; 119 | static char32_t const F7 = F6 + 1; 120 | static char32_t const F8 = F7 + 1; 121 | static char32_t const F9 = F8 + 1; 122 | static char32_t const F10 = F9 + 1; 123 | static char32_t const F11 = F10 + 1; 124 | static char32_t const F12 = F11 + 1; 125 | static char32_t const F13 = F12 + 1; 126 | static char32_t const F14 = F13 + 1; 127 | static char32_t const F15 = F14 + 1; 128 | static char32_t const F16 = F15 + 1; 129 | static char32_t const F17 = F16 + 1; 130 | static char32_t const F18 = F17 + 1; 131 | static char32_t const F19 = F18 + 1; 132 | static char32_t const F20 = F19 + 1; 133 | static char32_t const F21 = F20 + 1; 134 | static char32_t const F22 = F21 + 1; 135 | static char32_t const F23 = F22 + 1; 136 | static char32_t const F24 = F23 + 1; 137 | static char32_t const MOUSE = F24 + 1; 138 | static char32_t const PASTE_START = MOUSE + 1; 139 | static char32_t const PASTE_FINISH = PASTE_START + 1; 140 | static constexpr char32_t shift( char32_t key_ ) { 141 | return ( key_ | BASE_SHIFT ); 142 | } 143 | static constexpr char32_t control( char32_t key_ ) { 144 | return ( key_ | BASE_CONTROL ); 145 | } 146 | static constexpr char32_t meta( char32_t key_ ) { 147 | return ( key_ | BASE_META ); 148 | } 149 | static char32_t const BACKSPACE = 'H' | BASE_CONTROL; 150 | static char32_t const TAB = 'I' | BASE_CONTROL; 151 | static char32_t const ENTER = 'M' | BASE_CONTROL; 152 | static char32_t const ABORT = 'C' | BASE_CONTROL | BASE_META; 153 | }; 154 | /*! \brief List of built-in actions that act upon user input. 155 | */ 156 | enum class ACTION { 157 | INSERT_CHARACTER, 158 | NEW_LINE, 159 | DELETE_CHARACTER_UNDER_CURSOR, 160 | DELETE_CHARACTER_LEFT_OF_CURSOR, 161 | KILL_TO_END_OF_LINE, 162 | KILL_TO_BEGINING_OF_LINE, 163 | KILL_TO_END_OF_WORD, 164 | KILL_TO_BEGINING_OF_WORD, 165 | KILL_TO_END_OF_SUBWORD, 166 | KILL_TO_BEGINING_OF_SUBWORD, 167 | KILL_TO_WHITESPACE_ON_LEFT, 168 | YANK, 169 | YANK_CYCLE, 170 | YANK_LAST_ARG, 171 | MOVE_CURSOR_TO_BEGINING_OF_LINE, 172 | MOVE_CURSOR_TO_END_OF_LINE, 173 | MOVE_CURSOR_ONE_WORD_LEFT, 174 | MOVE_CURSOR_ONE_WORD_RIGHT, 175 | MOVE_CURSOR_ONE_SUBWORD_LEFT, 176 | MOVE_CURSOR_ONE_SUBWORD_RIGHT, 177 | MOVE_CURSOR_LEFT, 178 | MOVE_CURSOR_RIGHT, 179 | LINE_NEXT, 180 | LINE_PREVIOUS, 181 | HISTORY_NEXT, 182 | HISTORY_PREVIOUS, 183 | HISTORY_FIRST, 184 | HISTORY_LAST, 185 | HISTORY_RESTORE, 186 | HISTORY_RESTORE_CURRENT, 187 | HISTORY_INCREMENTAL_SEARCH, 188 | HISTORY_SEEDED_INCREMENTAL_SEARCH, 189 | HISTORY_COMMON_PREFIX_SEARCH, 190 | HINT_NEXT, 191 | HINT_PREVIOUS, 192 | CAPITALIZE_WORD, 193 | LOWERCASE_WORD, 194 | UPPERCASE_WORD, 195 | CAPITALIZE_SUBWORD, 196 | LOWERCASE_SUBWORD, 197 | UPPERCASE_SUBWORD, 198 | TRANSPOSE_CHARACTERS, 199 | TOGGLE_OVERWRITE_MODE, 200 | #ifndef _WIN32 201 | VERBATIM_INSERT, 202 | SUSPEND, 203 | #endif 204 | BRACKETED_PASTE, 205 | CLEAR_SCREEN, 206 | CLEAR_SELF, 207 | REPAINT, 208 | COMPLETE_LINE, 209 | COMPLETE_NEXT, 210 | COMPLETE_PREVIOUS, 211 | COMMIT_LINE, 212 | ABORT_LINE, 213 | SEND_EOF 214 | }; 215 | /*! \brief Possible results of key-press handler actions. 216 | */ 217 | enum class ACTION_RESULT { 218 | CONTINUE, /*!< Continue processing user input. */ 219 | RETURN, /*!< Return user input entered so far. */ 220 | BAIL /*!< Stop processing user input, returns nullptr from the \e input() call. */ 221 | }; 222 | typedef std::vector colors_t; 223 | class Completion { 224 | std::string _text; 225 | Color _color; 226 | public: 227 | Completion( char const* text_ ) 228 | : _text( text_ ) 229 | , _color( Color::DEFAULT ) { 230 | } 231 | Completion( std::string const& text_ ) 232 | : _text( text_ ) 233 | , _color( Color::DEFAULT ) { 234 | } 235 | Completion( std::string const& text_, Color color_ ) 236 | : _text( text_ ) 237 | , _color( color_ ) { 238 | } 239 | std::string const& text( void ) const { 240 | return ( _text ); 241 | } 242 | Color color( void ) const { 243 | return ( _color ); 244 | } 245 | }; 246 | typedef std::vector completions_t; 247 | class HistoryEntry { 248 | std::string _timestamp; 249 | std::string _text; 250 | public: 251 | HistoryEntry( std::string const& timestamp_, std::string const& text_ ) 252 | : _timestamp( timestamp_ ) 253 | , _text( text_ ) { 254 | } 255 | std::string const& timestamp( void ) const { 256 | return ( _timestamp ); 257 | } 258 | std::string const& text( void ) const { 259 | return ( _text ); 260 | } 261 | }; 262 | class HistoryScanImpl; 263 | class HistoryScan { 264 | public: 265 | typedef std::unique_ptr impl_t; 266 | private: 267 | #ifdef _MSC_VER 268 | #pragma warning(push) 269 | #pragma warning(disable:4251) 270 | #endif 271 | impl_t _impl; 272 | #ifdef _MSC_VER 273 | #pragma warning(pop) 274 | #endif 275 | public: 276 | HistoryScan( impl_t ); 277 | HistoryScan( HistoryScan&& ) = default; 278 | HistoryScan& operator = ( HistoryScan&& ) = default; 279 | bool next( void ); 280 | HistoryEntry const& get( void ) const; 281 | private: 282 | HistoryScan( HistoryScan const& ) = delete; 283 | HistoryScan& operator = ( HistoryScan const& ) = delete; 284 | }; 285 | typedef std::vector hints_t; 286 | 287 | /*! \brief Line modification callback type definition. 288 | * 289 | * User can observe and modify line contents (and cursor position) 290 | * in response to changes to both introduced by the user through 291 | * normal interactions. 292 | * 293 | * When callback returns Replxx updates current line content 294 | * and current cursor position to the ones updated by the callback. 295 | * 296 | * \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far. 297 | * \param cursorPosition[in,out] - a R/W reference to current cursor position. 298 | */ 299 | typedef std::function modify_callback_t; 300 | 301 | /*! \brief Completions callback type definition. 302 | * 303 | * \e contextLen is counted in Unicode code points (not in bytes!). 304 | * 305 | * For user input: 306 | * if ( obj.me 307 | * 308 | * input == "if ( obj.me" 309 | * contextLen == 2 (depending on \e set_word_break_characters()) 310 | * 311 | * Client application is free to update \e contextLen to be 6 (or any other non-negative 312 | * number not greater than the number of code points in input) if it makes better sense 313 | * for given client application semantics. 314 | * 315 | * \param input - UTF-8 encoded input entered by the user until current cursor position. 316 | * \param[in,out] contextLen - length of the additional context to provide while displaying completions. 317 | * \return A list of user completions. 318 | */ 319 | typedef std::function completion_callback_t; 320 | 321 | /*! \brief Highlighter callback type definition. 322 | * 323 | * If user want to have colorful input she must simply install highlighter callback. 324 | * The callback would be invoked by the library after each change to the input done by 325 | * the user. After callback returns library uses data from colors buffer to colorize 326 | * displayed user input. 327 | * 328 | * Size of \e colors buffer is equal to number of code points in user \e input 329 | * which will be different from simple `input.length()`! 330 | * 331 | * \param input - an UTF-8 encoded input entered by the user so far. 332 | * \param colors - output buffer for color information. 333 | */ 334 | typedef std::function highlighter_callback_t; 335 | 336 | /*! \brief Hints callback type definition. 337 | * 338 | * \e contextLen is counted in Unicode code points (not in bytes!). 339 | * 340 | * For user input: 341 | * if ( obj.me 342 | * 343 | * input == "if ( obj.me" 344 | * contextLen == 2 (depending on \e set_word_break_characters()) 345 | * 346 | * Client application is free to update \e contextLen to be 6 (or any other non-negative 347 | * number not greater than the number of code points in input) if it makes better sense 348 | * for given client application semantics. 349 | * 350 | * \param input - UTF-8 encoded input entered by the user until current cursor position. 351 | * \param contextLen[in,out] - length of the additional context to provide while displaying hints. 352 | * \param color - a color used for displaying hints. 353 | * \return A list of possible hints. 354 | */ 355 | typedef std::function hint_callback_t; 356 | 357 | /*! \brief Key press handler type definition. 358 | * 359 | * \param code - the key code replxx got from terminal. 360 | * \return Decision on how should input() behave after this key handler returns. 361 | */ 362 | typedef std::function key_press_handler_t; 363 | 364 | struct State { 365 | char const* _text; 366 | int _cursorPosition; 367 | State( char const* text_, int cursorPosition_ = -1 ) 368 | : _text( text_ ) 369 | , _cursorPosition( cursorPosition_ ) { 370 | } 371 | State( State const& ) = default; 372 | State& operator = ( State const& ) = default; 373 | char const* text( void ) const { 374 | return ( _text ); 375 | } 376 | int cursor_position( void ) const { 377 | return ( _cursorPosition ); 378 | } 379 | }; 380 | 381 | class ReplxxImpl; 382 | private: 383 | typedef std::unique_ptr impl_t; 384 | #ifdef _MSC_VER 385 | #pragma warning(push) 386 | #pragma warning(disable:4251) 387 | #endif 388 | impl_t _impl; 389 | #ifdef _MSC_VER 390 | #pragma warning(pop) 391 | #endif 392 | 393 | public: 394 | Replxx( void ); 395 | Replxx( Replxx&& ) = default; 396 | Replxx& operator = ( Replxx&& ) = default; 397 | 398 | /*! \brief Register modify callback. 399 | * 400 | * \param fn - user defined callback function. 401 | */ 402 | void set_modify_callback( modify_callback_t const& fn ); 403 | 404 | /*! \brief Register completion callback. 405 | * 406 | * \param fn - user defined callback function. 407 | */ 408 | void set_completion_callback( completion_callback_t const& fn ); 409 | 410 | /*! \brief Register highlighter callback. 411 | * 412 | * \param fn - user defined callback function. 413 | */ 414 | void set_highlighter_callback( highlighter_callback_t const& fn ); 415 | 416 | /*! \brief Register hints callback. 417 | * 418 | * \param fn - user defined callback function. 419 | */ 420 | void set_hint_callback( hint_callback_t const& fn ); 421 | 422 | /*! \brief Read line of user input. 423 | * 424 | * Returned pointer is managed by the library and is not to be freed in the client. 425 | * 426 | * \param prompt - prompt to be displayed before getting user input. 427 | * \return An UTF-8 encoded input given by the user (or nullptr on EOF). 428 | */ 429 | char const* input( std::string const& prompt ); 430 | 431 | /*! \brief Get current state data. 432 | * 433 | * This call is intended to be used in handlers. 434 | * 435 | * \return Current state of the model. 436 | */ 437 | State get_state( void ) const; 438 | 439 | /*! \brief Set new state data. 440 | * 441 | * This call is intended to be used in handlers. 442 | * 443 | * \param state - new state of the model. 444 | */ 445 | void set_state( State const& state ); 446 | 447 | /*! \brief Enable/disable case insensitive history search and completion. 448 | * 449 | * \param val - if set to non-zero then history search and completion will be case insensitive. 450 | */ 451 | void set_ignore_case( bool val ); 452 | 453 | /*! \brief Print formatted string to standard output. 454 | * 455 | * This function ensures proper handling of ANSI escape sequences 456 | * contained in printed data, which is especially useful on Windows 457 | * since Unixes handle them correctly out of the box. 458 | * 459 | * \param fmt - printf style format. 460 | */ 461 | void print( char const* fmt, ... ); 462 | 463 | /*! \brief Prints a char array with the given length to standard output. 464 | * 465 | * \copydetails print 466 | * 467 | * \param str - The char array to print. 468 | * \param length - The length of the array. 469 | */ 470 | void write( char const* str, int length ); 471 | 472 | /*! \brief Asynchronously change the prompt while replxx_input() call is in efect. 473 | * 474 | * Can be used to change the prompt from callbacks or other threads. 475 | * 476 | * \param prompt - The prompt string to change to. 477 | */ 478 | void set_prompt( std::string prompt ); 479 | 480 | /*! \brief Schedule an emulated key press event. 481 | * 482 | * \param code - key press code to be emulated. 483 | */ 484 | void emulate_key_press( char32_t code ); 485 | 486 | /*! \brief Invoke built-in action handler. 487 | * 488 | * \pre This method can be called only from key-press handler. 489 | * 490 | * \param action - a built-in action to invoke. 491 | * \param code - a supplementary key-code to consume by built-in action handler. 492 | * \return The action result informing the replxx what shall happen next. 493 | */ 494 | ACTION_RESULT invoke( ACTION action, char32_t code ); 495 | 496 | /*! \brief Bind user defined action to handle given key-press event. 497 | * 498 | * \param code - handle this key-press event with following handler. 499 | * \param handle - use this handler to handle key-press event. 500 | */ 501 | void bind_key( char32_t code, key_press_handler_t handler ); 502 | 503 | /*! \brief Bind internal `replxx` action (by name) to handle given key-press event. 504 | * 505 | * Action names are the same as names of Replxx::ACTION enumerations 506 | * but in lower case, e.g.: an action for recalling previous history line 507 | * is \e Replxx::ACTION::LINE_PREVIOUS so action name to be used in this 508 | * interface for the same effect is "line_previous". 509 | * 510 | * \param code - handle this key-press event with following handler. 511 | * \param actionName - name of internal action to be invoked on key press. 512 | */ 513 | void bind_key_internal( char32_t code, char const* actionName ); 514 | 515 | void history_add( std::string const& line ); 516 | 517 | /*! \brief Synchronize REPL's history with given file. 518 | * 519 | * Synchronizing means loading existing history from given file, 520 | * merging it with current history sorted by timestamps, 521 | * saving merged version to given file, 522 | * keeping merged version as current REPL's history. 523 | * 524 | * This call is an equivalent of calling: 525 | * history_save( "some-file" ); 526 | * history_load( "some-file" ); 527 | * 528 | * \param filename - a path to the file with which REPL's current history should be synchronized. 529 | * \return True iff history file was successfully created. 530 | */ 531 | bool history_sync( std::string const& filename ); 532 | 533 | /*! \brief Save REPL's history into given file. 534 | * 535 | * Saving means loading existing history from given file, 536 | * merging it with current history sorted by timestamps, 537 | * saving merged version to given file, 538 | * keeping original (NOT merged) version as current REPL's history. 539 | * 540 | * \param filename - a path to the file where REPL's history should be saved. 541 | * \return True iff history file was successfully created. 542 | */ 543 | bool history_save( std::string const& filename ); 544 | 545 | /*! 546 | * \copydoc history_save 547 | */ 548 | void history_save( std::ostream& out ); 549 | 550 | /*! \brief Load REPL's history from given file. 551 | * 552 | * \param filename - a path to the file which contains REPL's history that should be loaded. 553 | * \return True iff history file was successfully opened. 554 | */ 555 | bool history_load( std::string const& filename ); 556 | 557 | /*! 558 | * \copydoc history_load 559 | */ 560 | void history_load( std::istream& in ); 561 | 562 | /*! \brief Clear REPL's in-memory history. 563 | */ 564 | void history_clear( void ); 565 | int history_size( void ) const; 566 | HistoryScan history_scan( void ) const; 567 | 568 | void set_preload_buffer( std::string const& preloadText ); 569 | 570 | /*! \brief Set set of word break characters. 571 | * 572 | * This setting influences word based cursor movement and line editing capabilities. 573 | * 574 | * \param wordBreakers - 7-bit ASCII set of word breaking characters. 575 | */ 576 | void set_word_break_characters( char const* wordBreakers ); 577 | 578 | /*! \brief How many completions should trigger pagination. 579 | */ 580 | void set_completion_count_cutoff( int count ); 581 | 582 | /*! \brief Set maximum number of displayed hint rows. 583 | */ 584 | void set_max_hint_rows( int count ); 585 | 586 | /*! \brief Set a delay before hint are shown after user stopped typing.. 587 | * 588 | * \param milliseconds - a number of milliseconds to wait before showing hints. 589 | */ 590 | void set_hint_delay( int milliseconds ); 591 | 592 | /*! \brief Set tab completion behavior. 593 | * 594 | * \param val - use double tab to invoke completions. 595 | */ 596 | void set_double_tab_completion( bool val ); 597 | 598 | /*! \brief Set tab completion behavior. 599 | * 600 | * \param val - invoke completion even if user input is empty. 601 | */ 602 | void set_complete_on_empty( bool val ); 603 | 604 | /*! \brief Set tab completion behavior. 605 | * 606 | * \param val - beep if completion is ambiguous. 607 | */ 608 | void set_beep_on_ambiguous_completion( bool val ); 609 | 610 | /*! \brief Set complete next/complete previous behavior. 611 | * 612 | * COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations, 613 | * in case when a partial completion is possible complete only partial part (`false` setting) 614 | * or complete first proposed completion fully (`true` setting). 615 | * The default is to complete fully (a `true` setting - complete immediately). 616 | * 617 | * \param val - complete immediately. 618 | */ 619 | void set_immediate_completion( bool val ); 620 | 621 | /*! \brief Set history duplicate entries behaviour. 622 | * 623 | * \param val - should history contain only unique entries? 624 | */ 625 | void set_unique_history( bool val ); 626 | 627 | /*! \brief Disable output coloring. 628 | * 629 | * \param val - if set to non-zero disable output colors. 630 | */ 631 | void set_no_color( bool val ); 632 | 633 | /*! \brief Enable/disable (prompt width) indent for multiline entry. 634 | * 635 | * \param val - if set to true then multiline indent will be enabled. 636 | */ 637 | void set_indent_multiline( bool val ); 638 | 639 | /*! \brief Set maximum number of entries in history list. 640 | */ 641 | void set_max_history_size( int len ); 642 | void clear_screen( void ); 643 | int install_window_change_handler( void ); 644 | void enable_bracketed_paste( void ); 645 | void disable_bracketed_paste( void ); 646 | 647 | private: 648 | Replxx( Replxx const& ) = delete; 649 | Replxx& operator = ( Replxx const& ) = delete; 650 | }; 651 | 652 | /*! \brief Color definition related helper function. 653 | * 654 | * To be used to leverage 256 color terminal capabilities. 655 | */ 656 | namespace color { 657 | 658 | /*! \brief Combine two color definitions to get encompassing color definition. 659 | * 660 | * To be used only for combining foreground and background colors. 661 | * 662 | * \param color1 - first input color. 663 | * \param color2 - second input color. 664 | * \return A new color definition that represent combined input colors. 665 | */ 666 | Replxx::Color operator | ( Replxx::Color color1, Replxx::Color color2 ); 667 | 668 | /*! \brief Transform foreground color definition into a background color definition. 669 | * 670 | * \param color - an input foreground color definition. 671 | * \return A background color definition that is a transformed input \e color. 672 | */ 673 | Replxx::Color bg( Replxx::Color color ); 674 | 675 | /*! \brief Add `bold` attribute to color definition. 676 | * 677 | * \param color - an input color definition. 678 | * \return A new color definition with bold attribute set. 679 | */ 680 | Replxx::Color bold( Replxx::Color color ); 681 | 682 | /*! \brief Add `underline` attribute to color definition. 683 | * 684 | * \param color - an input color definition. 685 | * \return A new color definition with underline attribute set. 686 | */ 687 | Replxx::Color underline( Replxx::Color color ); 688 | 689 | /*! \brief Create a new grayscale color of given brightness level. 690 | * 691 | * \param level - a brightness level for new color, must be between 0 (darkest) and 23 (brightest). 692 | * \return A new grayscale color of a given brightest \e level. 693 | */ 694 | Replxx::Color grayscale( int level ); 695 | 696 | /*! \brief Create a new color in 6×6×6 RGB color space from base component levels. 697 | * 698 | * \param red - a red (of RGB) component level, must be 0 and 5. 699 | * \param green - a green (of RGB) component level, must be 0 and 5. 700 | * \param blue - a blue (of RGB) component level, must be 0 and 5. 701 | * \return A new color in 6×6×6 RGB color space. 702 | */ 703 | Replxx::Color rgb666( int red, int green, int blue ); 704 | 705 | } 706 | 707 | } 708 | 709 | #endif /* HAVE_REPLXX_HXX_INCLUDED */ 710 | 711 | -------------------------------------------------------------------------------- /include/replxx.h: -------------------------------------------------------------------------------- 1 | /* linenoise.h -- guerrilla line editing library against the idea that a 2 | * line editing lib needs to be 20,000 lines of C code. 3 | * 4 | * See linenoise.c for more information. 5 | * 6 | * Copyright (c) 2010, Salvatore Sanfilippo 7 | * Copyright (c) 2010, Pieter Noordhuis 8 | * 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without 12 | * modification, are permitted provided that the following conditions are met: 13 | * 14 | * * Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * * Neither the name of Redis nor the names of its contributors may be used 20 | * to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 27 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 30 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 31 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | * POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #ifndef __REPLXX_H 37 | #define __REPLXX_H 38 | 39 | #define REPLXX_VERSION "0.0.2" 40 | #define REPLXX_VERSION_MAJOR 0 41 | #define REPLXX_VERSION_MINOR 0 42 | 43 | #ifdef __cplusplus 44 | extern "C" { 45 | #endif 46 | 47 | /* 48 | * For use in Windows DLLs: 49 | * 50 | * If you are building replxx into a DLL, 51 | * unless you are using supplied CMake based build, 52 | * ensure that 'REPLXX_BUILDING_DLL' is defined when 53 | * building the DLL so that proper symbols are exported. 54 | */ 55 | #if defined( _WIN32 ) && ! defined( REPLXX_STATIC ) 56 | # ifdef REPLXX_BUILDING_DLL 57 | # define REPLXX_IMPEXP __declspec( dllexport ) 58 | # else 59 | # define REPLXX_IMPEXP __declspec( dllimport ) 60 | # endif 61 | #else 62 | # define REPLXX_IMPEXP /**/ 63 | #endif 64 | 65 | /*! \brief Color definitions to use in highlighter callbacks. 66 | */ 67 | typedef enum { 68 | REPLXX_COLOR_BLACK = 0, 69 | REPLXX_COLOR_RED = 1, 70 | REPLXX_COLOR_GREEN = 2, 71 | REPLXX_COLOR_BROWN = 3, 72 | REPLXX_COLOR_BLUE = 4, 73 | REPLXX_COLOR_MAGENTA = 5, 74 | REPLXX_COLOR_CYAN = 6, 75 | REPLXX_COLOR_LIGHTGRAY = 7, 76 | REPLXX_COLOR_GRAY = 8, 77 | REPLXX_COLOR_BRIGHTRED = 9, 78 | REPLXX_COLOR_BRIGHTGREEN = 10, 79 | REPLXX_COLOR_YELLOW = 11, 80 | REPLXX_COLOR_BRIGHTBLUE = 12, 81 | REPLXX_COLOR_BRIGHTMAGENTA = 13, 82 | REPLXX_COLOR_BRIGHTCYAN = 14, 83 | REPLXX_COLOR_WHITE = 15, 84 | REPLXX_COLOR_DEFAULT = 1u << 16u 85 | } ReplxxColor; 86 | 87 | enum { REPLXX_KEY_BASE = 0x0010ffff + 1 }; 88 | enum { REPLXX_KEY_BASE_SHIFT = 0x01000000 }; 89 | enum { REPLXX_KEY_BASE_CONTROL = 0x02000000 }; 90 | enum { REPLXX_KEY_BASE_META = 0x04000000 }; 91 | enum { REPLXX_KEY_ESCAPE = 27 }; 92 | enum { REPLXX_KEY_PAGE_UP = REPLXX_KEY_BASE + 1 }; 93 | enum { REPLXX_KEY_PAGE_DOWN = REPLXX_KEY_PAGE_UP + 1 }; 94 | enum { REPLXX_KEY_DOWN = REPLXX_KEY_PAGE_DOWN + 1 }; 95 | enum { REPLXX_KEY_UP = REPLXX_KEY_DOWN + 1 }; 96 | enum { REPLXX_KEY_LEFT = REPLXX_KEY_UP + 1 }; 97 | enum { REPLXX_KEY_RIGHT = REPLXX_KEY_LEFT + 1 }; 98 | enum { REPLXX_KEY_HOME = REPLXX_KEY_RIGHT + 1 }; 99 | enum { REPLXX_KEY_END = REPLXX_KEY_HOME + 1 }; 100 | enum { REPLXX_KEY_DELETE = REPLXX_KEY_END + 1 }; 101 | enum { REPLXX_KEY_INSERT = REPLXX_KEY_DELETE + 1 }; 102 | enum { REPLXX_KEY_F1 = REPLXX_KEY_INSERT + 1 }; 103 | enum { REPLXX_KEY_F2 = REPLXX_KEY_F1 + 1 }; 104 | enum { REPLXX_KEY_F3 = REPLXX_KEY_F2 + 1 }; 105 | enum { REPLXX_KEY_F4 = REPLXX_KEY_F3 + 1 }; 106 | enum { REPLXX_KEY_F5 = REPLXX_KEY_F4 + 1 }; 107 | enum { REPLXX_KEY_F6 = REPLXX_KEY_F5 + 1 }; 108 | enum { REPLXX_KEY_F7 = REPLXX_KEY_F6 + 1 }; 109 | enum { REPLXX_KEY_F8 = REPLXX_KEY_F7 + 1 }; 110 | enum { REPLXX_KEY_F9 = REPLXX_KEY_F8 + 1 }; 111 | enum { REPLXX_KEY_F10 = REPLXX_KEY_F9 + 1 }; 112 | enum { REPLXX_KEY_F11 = REPLXX_KEY_F10 + 1 }; 113 | enum { REPLXX_KEY_F12 = REPLXX_KEY_F11 + 1 }; 114 | enum { REPLXX_KEY_F13 = REPLXX_KEY_F12 + 1 }; 115 | enum { REPLXX_KEY_F14 = REPLXX_KEY_F13 + 1 }; 116 | enum { REPLXX_KEY_F15 = REPLXX_KEY_F14 + 1 }; 117 | enum { REPLXX_KEY_F16 = REPLXX_KEY_F15 + 1 }; 118 | enum { REPLXX_KEY_F17 = REPLXX_KEY_F16 + 1 }; 119 | enum { REPLXX_KEY_F18 = REPLXX_KEY_F17 + 1 }; 120 | enum { REPLXX_KEY_F19 = REPLXX_KEY_F18 + 1 }; 121 | enum { REPLXX_KEY_F20 = REPLXX_KEY_F19 + 1 }; 122 | enum { REPLXX_KEY_F21 = REPLXX_KEY_F20 + 1 }; 123 | enum { REPLXX_KEY_F22 = REPLXX_KEY_F21 + 1 }; 124 | enum { REPLXX_KEY_F23 = REPLXX_KEY_F22 + 1 }; 125 | enum { REPLXX_KEY_F24 = REPLXX_KEY_F23 + 1 }; 126 | enum { REPLXX_KEY_MOUSE = REPLXX_KEY_F24 + 1 }; 127 | enum { REPLXX_KEY_PASTE_START = REPLXX_KEY_MOUSE + 1 }; 128 | enum { REPLXX_KEY_PASTE_FINISH = REPLXX_KEY_PASTE_START + 1 }; 129 | 130 | #define REPLXX_KEY_SHIFT( key ) ( ( key ) | REPLXX_KEY_BASE_SHIFT ) 131 | #define REPLXX_KEY_CONTROL( key ) ( ( key ) | REPLXX_KEY_BASE_CONTROL ) 132 | #define REPLXX_KEY_META( key ) ( ( key ) | REPLXX_KEY_BASE_META ) 133 | 134 | enum { REPLXX_KEY_BACKSPACE = REPLXX_KEY_CONTROL( 'H' ) }; 135 | enum { REPLXX_KEY_TAB = REPLXX_KEY_CONTROL( 'I' ) }; 136 | enum { REPLXX_KEY_ENTER = REPLXX_KEY_CONTROL( 'M' ) }; 137 | enum { REPLXX_KEY_ABORT = REPLXX_KEY_META( REPLXX_KEY_CONTROL( 'M' ) ) }; 138 | 139 | /*! \brief List of built-in actions that act upon user input. 140 | */ 141 | typedef enum { 142 | REPLXX_ACTION_INSERT_CHARACTER, 143 | REPLXX_ACTION_NEW_LINE, 144 | REPLXX_ACTION_DELETE_CHARACTER_UNDER_CURSOR, 145 | REPLXX_ACTION_DELETE_CHARACTER_LEFT_OF_CURSOR, 146 | REPLXX_ACTION_KILL_TO_END_OF_LINE, 147 | REPLXX_ACTION_KILL_TO_BEGINING_OF_LINE, 148 | REPLXX_ACTION_KILL_TO_END_OF_WORD, 149 | REPLXX_ACTION_KILL_TO_BEGINING_OF_WORD, 150 | REPLXX_ACTION_KILL_TO_END_OF_SUBWORD, 151 | REPLXX_ACTION_KILL_TO_BEGINING_OF_SUBWORD, 152 | REPLXX_ACTION_KILL_TO_WHITESPACE_ON_LEFT, 153 | REPLXX_ACTION_YANK, 154 | REPLXX_ACTION_YANK_CYCLE, 155 | REPLXX_ACTION_YANK_LAST_ARG, 156 | REPLXX_ACTION_MOVE_CURSOR_TO_BEGINING_OF_LINE, 157 | REPLXX_ACTION_MOVE_CURSOR_TO_END_OF_LINE, 158 | REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_LEFT, 159 | REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_RIGHT, 160 | REPLXX_ACTION_MOVE_CURSOR_ONE_SUBWORD_LEFT, 161 | REPLXX_ACTION_MOVE_CURSOR_ONE_SUBWORD_RIGHT, 162 | REPLXX_ACTION_MOVE_CURSOR_LEFT, 163 | REPLXX_ACTION_MOVE_CURSOR_RIGHT, 164 | REPLXX_ACTION_LINE_NEXT, 165 | REPLXX_ACTION_LINE_PREVIOUS, 166 | REPLXX_ACTION_HISTORY_MOVE_NEXT, 167 | REPLXX_ACTION_HISTORY_MOVE_PREVIOUS, 168 | REPLXX_ACTION_HISTORY_FIRST, 169 | REPLXX_ACTION_HISTORY_LAST, 170 | REPLXX_ACTION_HISTORY_RESTORE, 171 | REPLXX_ACTION_HISTORY_RESTORE_CURRENT, 172 | REPLXX_ACTION_HISTORY_INCREMENTAL_SEARCH, 173 | REPLXX_ACTION_HISTORY_SEEDED_INCREMENTAL_SEARCH, 174 | REPLXX_ACTION_HISTORY_COMMON_PREFIX_SEARCH, 175 | REPLXX_ACTION_HINT_NEXT, 176 | REPLXX_ACTION_HINT_PREVIOUS, 177 | REPLXX_ACTION_CAPITALIZE_WORD, 178 | REPLXX_ACTION_LOWERCASE_WORD, 179 | REPLXX_ACTION_UPPERCASE_WORD, 180 | REPLXX_ACTION_CAPITALIZE_SUBWORD, 181 | REPLXX_ACTION_LOWERCASE_SUBWORD, 182 | REPLXX_ACTION_UPPERCASE_SUBWORD, 183 | REPLXX_ACTION_TRANSPOSE_CHARACTERS, 184 | REPLXX_ACTION_TOGGLE_OVERWRITE_MODE, 185 | #ifndef _WIN32 186 | REPLXX_ACTION_VERBATIM_INSERT, 187 | REPLXX_ACTION_SUSPEND, 188 | #endif 189 | REPLXX_ACTION_BRACKETED_PASTE, 190 | REPLXX_ACTION_CLEAR_SCREEN, 191 | REPLXX_ACTION_CLEAR_SELF, 192 | REPLXX_ACTION_REPAINT, 193 | REPLXX_ACTION_COMPLETE_LINE, 194 | REPLXX_ACTION_COMPLETE_NEXT, 195 | REPLXX_ACTION_COMPLETE_PREVIOUS, 196 | REPLXX_ACTION_COMMIT_LINE, 197 | REPLXX_ACTION_ABORT_LINE, 198 | REPLXX_ACTION_SEND_EOF 199 | } ReplxxAction; 200 | 201 | /*! \brief Possible results of key-press handler actions. 202 | */ 203 | typedef enum { 204 | REPLXX_ACTION_RESULT_CONTINUE, /*!< Continue processing user input. */ 205 | REPLXX_ACTION_RESULT_RETURN, /*!< Return user input entered so far. */ 206 | REPLXX_ACTION_RESULT_BAIL /*!< Stop processing user input, returns nullptr from the \e input() call. */ 207 | } ReplxxActionResult; 208 | 209 | typedef struct ReplxxStateTag { 210 | char const* text; 211 | int cursorPosition; 212 | } ReplxxState; 213 | 214 | typedef struct Replxx Replxx; 215 | typedef struct ReplxxHistoryScan ReplxxHistoryScan; 216 | typedef struct ReplxxHistoryEntryTag { 217 | char const* timestamp; 218 | char const* text; 219 | } ReplxxHistoryEntry; 220 | 221 | /*! \brief Create Replxx library resource holder. 222 | * 223 | * Use replxx_end() to free resources acquired with this function. 224 | * 225 | * \return Replxx library resource holder. 226 | */ 227 | REPLXX_IMPEXP Replxx* replxx_init( void ); 228 | 229 | /*! \brief Cleanup resources used by Replxx library. 230 | * 231 | * \param replxx - a Replxx library resource holder. 232 | */ 233 | REPLXX_IMPEXP void replxx_end( Replxx* replxx ); 234 | 235 | /*! \brief Line modification callback type definition. 236 | * 237 | * User can observe and modify line contents (and cursor position) 238 | * in response to changes to both introduced by the user through 239 | * normal interactions. 240 | * 241 | * When callback returns Replxx updates current line content 242 | * and current cursor position to the ones updated by the callback. 243 | * 244 | * \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far. 245 | * \param cursorPosition[in,out] - a R/W reference to current cursor position. 246 | * \param userData - pointer to opaque user data block. 247 | */ 248 | typedef void (replxx_modify_callback_t)(char** input, int* contextLen, void* userData); 249 | 250 | /*! \brief Register modify callback. 251 | * 252 | * \param fn - user defined callback function. 253 | * \param userData - pointer to opaque user data block to be passed into each invocation of the callback. 254 | */ 255 | REPLXX_IMPEXP void replxx_set_modify_callback( Replxx*, replxx_modify_callback_t* fn, void* userData ); 256 | 257 | /*! \brief Highlighter callback type definition. 258 | * 259 | * If user want to have colorful input she must simply install highlighter callback. 260 | * The callback would be invoked by the library after each change to the input done by 261 | * the user. After callback returns library uses data from colors buffer to colorize 262 | * displayed user input. 263 | * 264 | * \e size of \e colors buffer is equal to number of code points in user \e input 265 | * which will be different from simple `strlen( input )`! 266 | * 267 | * \param input - an UTF-8 encoded input entered by the user so far. 268 | * \param colors - output buffer for color information. 269 | * \param size - size of output buffer for color information. 270 | * \param userData - pointer to opaque user data block. 271 | */ 272 | typedef void (replxx_highlighter_callback_t)(char const* input, ReplxxColor* colors, int size, void* userData); 273 | 274 | /*! \brief Register highlighter callback. 275 | * 276 | * \param fn - user defined callback function. 277 | * \param userData - pointer to opaque user data block to be passed into each invocation of the callback. 278 | */ 279 | REPLXX_IMPEXP void replxx_set_highlighter_callback( Replxx*, replxx_highlighter_callback_t* fn, void* userData ); 280 | 281 | typedef struct replxx_completions replxx_completions; 282 | 283 | /*! \brief Completions callback type definition. 284 | * 285 | * \e contextLen is counted in Unicode code points (not in bytes!). 286 | * 287 | * For user input: 288 | * if ( obj.me 289 | * 290 | * input == "if ( obj.me" 291 | * contextLen == 2 (depending on \e replxx_set_word_break_characters()) 292 | * 293 | * Client application is free to update \e contextLen to be 6 (or any other non-negative 294 | * number not greater than the number of code points in input) if it makes better sense 295 | * for given client application semantics. 296 | * 297 | * \param input - UTF-8 encoded input entered by the user until current cursor position. 298 | * \param completions - pointer to opaque list of user completions. 299 | * \param contextLen[in,out] - length of the additional context to provide while displaying completions. 300 | * \param userData - pointer to opaque user data block. 301 | */ 302 | typedef void(replxx_completion_callback_t)(const char* input, replxx_completions* completions, int* contextLen, void* userData); 303 | 304 | /*! \brief Register completion callback. 305 | * 306 | * \param fn - user defined callback function. 307 | * \param userData - pointer to opaque user data block to be passed into each invocation of the callback. 308 | */ 309 | REPLXX_IMPEXP void replxx_set_completion_callback( Replxx*, replxx_completion_callback_t* fn, void* userData ); 310 | 311 | /*! \brief Add another possible completion for current user input. 312 | * 313 | * \param completions - pointer to opaque list of user completions. 314 | * \param str - UTF-8 encoded completion string. 315 | */ 316 | REPLXX_IMPEXP void replxx_add_completion( replxx_completions* completions, const char* str ); 317 | 318 | /*! \brief Add another possible completion for current user input. 319 | * 320 | * \param completions - pointer to opaque list of user completions. 321 | * \param str - UTF-8 encoded completion string. 322 | * \param color - a color for the completion. 323 | */ 324 | REPLXX_IMPEXP void replxx_add_color_completion( replxx_completions* completions, const char* str, ReplxxColor color ); 325 | 326 | typedef struct replxx_hints replxx_hints; 327 | 328 | /*! \brief Hints callback type definition. 329 | * 330 | * \e contextLen is counted in Unicode code points (not in bytes!). 331 | * 332 | * For user input: 333 | * if ( obj.me 334 | * 335 | * input == "if ( obj.me" 336 | * contextLen == 2 (depending on \e replxx_set_word_break_characters()) 337 | * 338 | * Client application is free to update \e contextLen to be 6 (or any other non-negative 339 | * number not greater than the number of code points in input) if it makes better sense 340 | * for given client application semantics. 341 | * 342 | * \param input - UTF-8 encoded input entered by the user until current cursor position. 343 | * \param hints - pointer to opaque list of possible hints. 344 | * \param contextLen[in,out] - length of the additional context to provide while displaying hints. 345 | * \param color - a color used for displaying hints. 346 | * \param userData - pointer to opaque user data block. 347 | */ 348 | typedef void(replxx_hint_callback_t)(const char* input, replxx_hints* hints, int* contextLen, ReplxxColor* color, void* userData); 349 | 350 | /*! \brief Register hints callback. 351 | * 352 | * \param fn - user defined callback function. 353 | * \param userData - pointer to opaque user data block to be passed into each invocation of the callback. 354 | */ 355 | REPLXX_IMPEXP void replxx_set_hint_callback( Replxx*, replxx_hint_callback_t* fn, void* userData ); 356 | 357 | /*! \brief Key press handler type definition. 358 | * 359 | * \param code - the key code replxx got from terminal. 360 | * \return Decision on how should input() behave after this key handler returns. 361 | */ 362 | typedef ReplxxActionResult (key_press_handler_t)( int code, void* userData ); 363 | 364 | /*! \brief Add another possible hint for current user input. 365 | * 366 | * \param hints - pointer to opaque list of hints. 367 | * \param str - UTF-8 encoded hint string. 368 | */ 369 | REPLXX_IMPEXP void replxx_add_hint( replxx_hints* hints, const char* str ); 370 | 371 | /*! \brief Read line of user input. 372 | * 373 | * Returned pointer is managed by the library and is not to be freed in the client. 374 | * 375 | * \param prompt - prompt to be displayed before getting user input. 376 | * \return An UTF-8 encoded input given by the user (or nullptr on EOF). 377 | */ 378 | REPLXX_IMPEXP char const* replxx_input( Replxx*, const char* prompt ); 379 | 380 | /*! \brief Get current state data. 381 | * 382 | * This call is intended to be used in handlers. 383 | * 384 | * \param state - buffer for current state of the model. 385 | */ 386 | REPLXX_IMPEXP void replxx_get_state( Replxx*, ReplxxState* state ); 387 | 388 | /*! \brief Set new state data. 389 | * 390 | * This call is intended to be used in handlers. 391 | * 392 | * \param state - new state of the model. 393 | */ 394 | REPLXX_IMPEXP void replxx_set_state( Replxx*, ReplxxState* state ); 395 | 396 | /*! \brief Enable/disable case insensitive history search and completion. 397 | * 398 | * \param val - if set to non-zero then history search and completion will be case insensitive. 399 | */ 400 | REPLXX_IMPEXP void replxx_set_ignore_case( Replxx*, int val ); 401 | 402 | /*! \brief Print formatted string to standard output. 403 | * 404 | * This function ensures proper handling of ANSI escape sequences 405 | * contained in printed data, which is especially useful on Windows 406 | * since Unixes handle them correctly out of the box. 407 | * 408 | * \param fmt - printf style format. 409 | */ 410 | REPLXX_IMPEXP int replxx_print( Replxx*, char const* fmt, ... ); 411 | 412 | /*! \brief Prints a char array with the given length to standard output. 413 | * 414 | * \copydetails print 415 | * 416 | * \param str - The char array to print. 417 | * \param length - The length of the array. 418 | */ 419 | REPLXX_IMPEXP int replxx_write( Replxx*, char const* str, int length ); 420 | 421 | /*! \brief Asynchronously change the prompt while replxx_input() call is in efect. 422 | * 423 | * Can be used to change the prompt from callbacks or other threads. 424 | * 425 | * \param prompt - The prompt string to change to. 426 | */ 427 | REPLXX_IMPEXP void replxx_set_prompt( Replxx*, const char* prompt ); 428 | 429 | /*! \brief Schedule an emulated key press event. 430 | * 431 | * \param code - key press code to be emulated. 432 | */ 433 | REPLXX_IMPEXP void replxx_emulate_key_press( Replxx*, int unsigned code ); 434 | 435 | /*! \brief Invoke built-in action handler. 436 | * 437 | * \pre This function can be called only from key-press handler. 438 | * 439 | * \param action - a built-in action to invoke. 440 | * \param code - a supplementary key-code to consume by built-in action handler. 441 | * \return The action result informing the replxx what shall happen next. 442 | */ 443 | REPLXX_IMPEXP ReplxxActionResult replxx_invoke( Replxx*, ReplxxAction action, int unsigned code ); 444 | 445 | /*! \brief Bind user defined action to handle given key-press event. 446 | * 447 | * \param code - handle this key-press event with following handler. 448 | * \param handler - use this handler to handle key-press event. 449 | * \param userData - supplementary user data passed to invoked handlers. 450 | */ 451 | REPLXX_IMPEXP void replxx_bind_key( Replxx*, int code, key_press_handler_t handler, void* userData ); 452 | 453 | /*! \brief Bind internal `replxx` action (by name) to handle given key-press event. 454 | * 455 | * Action names are the same as unique part of names of ReplxxAction enumerations 456 | * but in lower case, e.g.: an action for recalling previous history line 457 | * is \e REPLXX_ACTION_LINE_PREVIOUS so action name to be used in this 458 | * interface for the same effect is "line_previous". 459 | * 460 | * \param code - handle this key-press event with following handler. 461 | * \param actionName - name of internal action to be invoked on key press. 462 | * \return -1 if invalid action name was used, 0 otherwise. 463 | */ 464 | int replxx_bind_key_internal( Replxx*, int code, char const* actionName ); 465 | 466 | REPLXX_IMPEXP void replxx_set_preload_buffer( Replxx*, const char* preloadText ); 467 | 468 | REPLXX_IMPEXP void replxx_history_add( Replxx*, const char* line ); 469 | REPLXX_IMPEXP int replxx_history_size( Replxx* ); 470 | 471 | /*! \brief Set set of word break characters. 472 | * 473 | * This setting influences word based cursor movement and line editing capabilities. 474 | * 475 | * \param wordBreakers - 7-bit ASCII set of word breaking characters. 476 | */ 477 | REPLXX_IMPEXP void replxx_set_word_break_characters( Replxx*, char const* wordBreakers ); 478 | 479 | /*! \brief How many completions should trigger pagination. 480 | */ 481 | REPLXX_IMPEXP void replxx_set_completion_count_cutoff( Replxx*, int count ); 482 | 483 | /*! \brief Set maximum number of displayed hint rows. 484 | */ 485 | REPLXX_IMPEXP void replxx_set_max_hint_rows( Replxx*, int count ); 486 | 487 | /*! \brief Set a delay before hint are shown after user stopped typing.. 488 | * 489 | * \param milliseconds - a number of milliseconds to wait before showing hints. 490 | */ 491 | REPLXX_IMPEXP void replxx_set_hint_delay( Replxx*, int milliseconds ); 492 | 493 | /*! \brief Set tab completion behavior. 494 | * 495 | * \param val - use double tab to invoke completions (if != 0). 496 | */ 497 | REPLXX_IMPEXP void replxx_set_double_tab_completion( Replxx*, int val ); 498 | 499 | /*! \brief Set tab completion behavior. 500 | * 501 | * \param val - invoke completion even if user input is empty (if != 0). 502 | */ 503 | REPLXX_IMPEXP void replxx_set_complete_on_empty( Replxx*, int val ); 504 | 505 | /*! \brief Set tab completion behavior. 506 | * 507 | * \param val - beep if completion is ambiguous (if != 0). 508 | */ 509 | REPLXX_IMPEXP void replxx_set_beep_on_ambiguous_completion( Replxx*, int val ); 510 | 511 | /*! \brief Set complete next/complete previous behavior. 512 | * 513 | * COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations, 514 | * in case when a partial completion is possible complete only partial part (`false` setting) 515 | * or complete first proposed completion fully (`true` setting). 516 | * The default is to complete fully (a `true` setting - complete immediately). 517 | * 518 | * \param val - complete immediately. 519 | */ 520 | REPLXX_IMPEXP void replxx_set_immediate_completion( Replxx*, int val ); 521 | 522 | /*! \brief Set history duplicate entries behaviour. 523 | * 524 | * \param val - should history contain only unique entries? 525 | */ 526 | REPLXX_IMPEXP void replxx_set_unique_history( Replxx*, int val ); 527 | 528 | /*! \brief Disable output coloring. 529 | * 530 | * \param val - if set to non-zero disable output colors. 531 | */ 532 | REPLXX_IMPEXP void replxx_set_no_color( Replxx*, int val ); 533 | 534 | /*! \brief Enable/disable (prompt width) indent for multiline entry. 535 | * 536 | * \param val - if set to non-zero then multiline indent will be enabled. 537 | */ 538 | REPLXX_IMPEXP void replxx_set_indent_multiline( Replxx*, int val ); 539 | 540 | /*! \brief Set maximum number of entries in history list. 541 | */ 542 | REPLXX_IMPEXP void replxx_set_max_history_size( Replxx*, int len ); 543 | REPLXX_IMPEXP ReplxxHistoryScan* replxx_history_scan_start( Replxx* ); 544 | REPLXX_IMPEXP void replxx_history_scan_stop( Replxx*, ReplxxHistoryScan* ); 545 | REPLXX_IMPEXP int replxx_history_scan_next( Replxx*, ReplxxHistoryScan*, ReplxxHistoryEntry* ); 546 | 547 | /*! \brief Synchronize REPL's history with given file. 548 | * 549 | * Synchronizing means loading existing history from given file, 550 | * merging it with current history sorted by timestamps, 551 | * saving merged version to given file, 552 | * keeping merged version as current REPL's history. 553 | * 554 | * This call is an equivalent of calling: 555 | * replxx_history_save( rx, "some-file" ); 556 | * replxx_history_load( rx, "some-file" ); 557 | * 558 | * \param filename - a path to the file with which REPL's current history should be synchronized. 559 | * \return 0 iff history file was successfully created, -1 otherwise. 560 | */ 561 | REPLXX_IMPEXP int replxx_history_sync( Replxx*, const char* filename ); 562 | 563 | /*! \brief Save REPL's history into given file. 564 | * 565 | * Saving means loading existing history from given file, 566 | * merging it with current history sorted by timestamps, 567 | * saving merged version to given file, 568 | * keeping original (NOT merged) version as current REPL's history. 569 | * 570 | * \param filename - a path to the file where REPL's history should be saved. 571 | * \return 0 iff history file was successfully created, -1 otherwise. 572 | */ 573 | REPLXX_IMPEXP int replxx_history_save( Replxx*, const char* filename ); 574 | 575 | /*! \brief Load REPL's history from given file. 576 | * 577 | * \param filename - a path to the file which contains REPL's history that should be loaded. 578 | * \return 0 iff history file was successfully opened, -1 otherwise. 579 | */ 580 | REPLXX_IMPEXP int replxx_history_load( Replxx*, const char* filename ); 581 | 582 | /*! \brief Clear REPL's in-memory history. 583 | */ 584 | REPLXX_IMPEXP void replxx_history_clear( Replxx* ); 585 | REPLXX_IMPEXP void replxx_clear_screen( Replxx* ); 586 | #ifdef __REPLXX_DEBUG__ 587 | void replxx_debug_dump_print_codes(void); 588 | #endif 589 | /* the following is extension to the original linenoise API */ 590 | REPLXX_IMPEXP int replxx_install_window_change_handler( Replxx* ); 591 | REPLXX_IMPEXP void replxx_enable_bracketed_paste( Replxx* ); 592 | REPLXX_IMPEXP void replxx_disable_bracketed_paste( Replxx* ); 593 | 594 | /*! \brief Combine two color definitions to get encompassing color definition. 595 | * 596 | * To be used only for combining foreground and background colors. 597 | * 598 | * \param color1 - first input color. 599 | * \param color2 - second input color. 600 | * \return A new color definition that represent combined input colors. 601 | */ 602 | ReplxxColor replxx_color_combine( ReplxxColor color1, ReplxxColor color2 ); 603 | 604 | /*! \brief Transform foreground color definition into a background color definition. 605 | * 606 | * \param color - an input foreground color definition. 607 | * \return A background color definition that is a transformed input \e color. 608 | */ 609 | ReplxxColor replxx_color_bg( ReplxxColor color ); 610 | 611 | /*! \brief Add `bold` attribute to color definition. 612 | * 613 | * \param color - an input color definition. 614 | * \return A new color definition with bold attribute set. 615 | */ 616 | ReplxxColor replxx_color_bold( ReplxxColor color ); 617 | 618 | /*! \brief Add `underline` attribute to color definition. 619 | * 620 | * \param color - an input color definition. 621 | * \return A new color definition with underline attribute set. 622 | */ 623 | ReplxxColor replxx_color_underline( ReplxxColor color ); 624 | 625 | /*! \brief Create a new grayscale color of given brightness level. 626 | * 627 | * \param level - a brightness level for new color, must be between 0 (darkest) and 23 (brightest). 628 | * \return A new grayscale color of a given brightest \e level. 629 | */ 630 | ReplxxColor replxx_color_grayscale( int level ); 631 | 632 | /*! \brief Create a new color in 6×6×6 RGB color space from base component levels. 633 | * 634 | * \param red - a red (of RGB) component level, must be 0 and 5. 635 | * \param green - a green (of RGB) component level, must be 0 and 5. 636 | * \param blue - a blue (of RGB) component level, must be 0 and 5. 637 | * \return A new color in 6×6×6 RGB color space. 638 | */ 639 | ReplxxColor replxx_color_rgb666( int red, int green, int blue ); 640 | 641 | #ifdef __cplusplus 642 | } 643 | #endif 644 | 645 | #endif /* __REPLXX_H */ 646 | 647 | -------------------------------------------------------------------------------- /examples/cxx-api.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "replxx.hxx" 16 | #include "util.h" 17 | 18 | using Replxx = replxx::Replxx; 19 | using namespace replxx::color; 20 | 21 | class Tick { 22 | typedef std::vector keys_t; 23 | std::thread _thread; 24 | int _tick; 25 | int _promptState; 26 | bool _alive; 27 | keys_t _keys; 28 | bool _tickMessages; 29 | bool _promptFan; 30 | Replxx& _replxx; 31 | public: 32 | Tick( Replxx& replxx_, std::string const& keys_, bool tickMessages_, bool promptFan_ ) 33 | : _thread() 34 | , _tick( 0 ) 35 | , _promptState( 0 ) 36 | , _alive( false ) 37 | , _keys( keys_.begin(), keys_.end() ) 38 | , _tickMessages( tickMessages_ ) 39 | , _promptFan( promptFan_ ) 40 | , _replxx( replxx_ ) { 41 | } 42 | void start() { 43 | _alive = true; 44 | _thread = std::thread( &Tick::run, this ); 45 | } 46 | void stop() { 47 | _alive = false; 48 | _thread.join(); 49 | } 50 | void run() { 51 | std::string s; 52 | static char const PROMPT_STATES[] = "-\\|/"; 53 | while ( _alive ) { 54 | if ( _tickMessages ) { 55 | _replxx.print( "%d\n", _tick ); 56 | } 57 | if ( _tick < static_cast( _keys.size() ) ) { 58 | _replxx.emulate_key_press( _keys[_tick] ); 59 | } 60 | if ( ! _tickMessages && ! _promptFan && ( _tick >= static_cast( _keys.size() ) ) ) { 61 | break; 62 | } 63 | if ( _promptFan ) { 64 | for ( int i( 0 ); i < 4; ++ i ) { 65 | char prompt[] = "\x1b[1;32mreplxx\x1b[0m[ ]> "; 66 | prompt[18] = PROMPT_STATES[_promptState % 4]; 67 | ++ _promptState; 68 | _replxx.set_prompt( prompt ); 69 | std::this_thread::sleep_for( std::chrono::milliseconds( 250 ) ); 70 | } 71 | } else { 72 | std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); 73 | } 74 | ++ _tick; 75 | } 76 | } 77 | }; 78 | 79 | // prototypes 80 | Replxx::completions_t hook_completion(std::string const& context, int& contextLen, std::vector const& user_data, bool); 81 | Replxx::hints_t hook_hint(std::string const& context, int& contextLen, Replxx::Color& color, std::vector const& user_data, bool); 82 | typedef std::vector> syntax_highlight_t; 83 | typedef std::unordered_map keyword_highlight_t; 84 | void hook_color( std::string const& str, Replxx::colors_t& colors, syntax_highlight_t const&, keyword_highlight_t const& ); 85 | void hook_modify( std::string& line, int& cursorPosition, Replxx* ); 86 | 87 | bool eq( std::string const& l, std::string const& r, int s, bool ic ) { 88 | if ( static_cast( l.length() ) < s ) { 89 | return false; 90 | } 91 | if ( static_cast( r.length() ) < s ) { 92 | return false; 93 | } 94 | bool same( true ); 95 | for ( int i( 0 ); same && ( i < s ); ++ i ) { 96 | same = ( ic && ( towlower( l[i] ) == towlower( r[i] ) ) ) || ( l[i] == r[i] ); 97 | } 98 | return same; 99 | } 100 | 101 | Replxx::completions_t hook_completion(std::string const& context, int& contextLen, std::vector const& examples, bool ignoreCase) { 102 | Replxx::completions_t completions; 103 | int utf8ContextLen( context_len( context.c_str() ) ); 104 | int prefixLen( static_cast( context.length() ) - utf8ContextLen ); 105 | if ( ( prefixLen > 0 ) && ( context[prefixLen - 1] == '\\' ) ) { 106 | -- prefixLen; 107 | ++ utf8ContextLen; 108 | } 109 | contextLen = utf8str_codepoint_len( context.c_str() + prefixLen, utf8ContextLen ); 110 | 111 | std::string prefix { context.substr(prefixLen) }; 112 | if ( prefix == "\\pi" ) { 113 | completions.push_back( "π" ); 114 | } else { 115 | for (auto const& e : examples) { 116 | bool lowerCasePrefix( std::none_of( prefix.begin(), prefix.end(), iswupper ) ); 117 | if ( eq( e, prefix, static_cast( prefix.size() ), ignoreCase && lowerCasePrefix ) ) { 118 | Replxx::Color c( Replxx::Color::DEFAULT ); 119 | if ( e.find( "brightred" ) != std::string::npos ) { 120 | c = Replxx::Color::BRIGHTRED; 121 | } else if ( e.find( "red" ) != std::string::npos ) { 122 | c = Replxx::Color::RED; 123 | } 124 | completions.emplace_back(e.c_str(), c); 125 | } 126 | } 127 | } 128 | 129 | return completions; 130 | } 131 | 132 | Replxx::hints_t hook_hint(std::string const& context, int& contextLen, Replxx::Color& color, std::vector const& examples, bool ignoreCase) { 133 | Replxx::hints_t hints; 134 | 135 | // only show hint if prefix is at least 'n' chars long 136 | // or if prefix begins with a specific character 137 | 138 | int utf8ContextLen( context_len( context.c_str() ) ); 139 | int prefixLen( static_cast( context.length() ) - utf8ContextLen ); 140 | contextLen = utf8str_codepoint_len( context.c_str() + prefixLen, utf8ContextLen ); 141 | std::string prefix { context.substr(prefixLen) }; 142 | 143 | if (prefix.size() >= 2 || (! prefix.empty() && prefix.at(0) == '.')) { 144 | bool lowerCasePrefix( std::none_of( prefix.begin(), prefix.end(), iswupper ) ); 145 | for (auto const& e : examples) { 146 | if ( eq( e, prefix, prefix.size(), ignoreCase && lowerCasePrefix ) ) { 147 | hints.emplace_back(e.c_str()); 148 | } 149 | } 150 | } 151 | 152 | // set hint color to green if single match found 153 | if (hints.size() == 1) { 154 | color = Replxx::Color::GREEN; 155 | } 156 | 157 | return hints; 158 | } 159 | 160 | inline bool is_kw( char ch ) { 161 | return isalnum( ch ) || ( ch == '_' ); 162 | } 163 | 164 | void hook_color( std::string const& context, Replxx::colors_t& colors, syntax_highlight_t const& regex_color, keyword_highlight_t const& word_color ) { 165 | // highlight matching regex sequences 166 | for (auto const& e : regex_color) { 167 | size_t pos {0}; 168 | std::string str = context; 169 | std::smatch match; 170 | 171 | while(std::regex_search(str, match, std::regex(e.first))) { 172 | std::string c{ match[0] }; 173 | std::string prefix( match.prefix().str() ); 174 | pos += utf8str_codepoint_len( prefix.c_str(), static_cast( prefix.length() ) ); 175 | int len( utf8str_codepoint_len( c.c_str(), static_cast( c.length() ) ) ); 176 | 177 | for (int i = 0; i < len; ++i) { 178 | colors.at(pos + i) = e.second; 179 | } 180 | 181 | pos += len; 182 | str = match.suffix(); 183 | } 184 | } 185 | bool inWord( false ); 186 | int wordStart( 0 ); 187 | int wordEnd( 0 ); 188 | int colorOffset( 0 ); 189 | auto dohl = [&](int i) { 190 | inWord = false; 191 | std::string intermission( context.substr( wordEnd, wordStart - wordEnd ) ); 192 | colorOffset += utf8str_codepoint_len( intermission.c_str(), intermission.length() ); 193 | int wordLen( i - wordStart ); 194 | std::string keyword( context.substr( wordStart, wordLen ) ); 195 | bool bold( false ); 196 | if ( keyword.substr( 0, 5 ) == "bold_" ) { 197 | keyword = keyword.substr( 5 ); 198 | bold = true; 199 | } 200 | bool underline( false ); 201 | if ( keyword.substr( 0, 10 ) == "underline_" ) { 202 | keyword = keyword.substr( 10 ); 203 | underline = true; 204 | } 205 | keyword_highlight_t::const_iterator it( word_color.find( keyword ) ); 206 | Replxx::Color color = Replxx::Color::DEFAULT; 207 | if ( it != word_color.end() ) { 208 | color = it->second; 209 | } 210 | if ( bold ) { 211 | color = replxx::color::bold( color ); 212 | } 213 | if ( underline ) { 214 | color = replxx::color::underline( color ); 215 | } 216 | for ( int k( 0 ); k < wordLen; ++ k ) { 217 | Replxx::Color& c( colors.at( colorOffset + k ) ); 218 | if ( color != Replxx::Color::DEFAULT ) { 219 | c = color; 220 | } 221 | } 222 | colorOffset += wordLen; 223 | wordEnd = i; 224 | }; 225 | for ( int i( 0 ); i < static_cast( context.length() ); ++ i ) { 226 | if ( !inWord ) { 227 | if ( is_kw( context[i] ) ) { 228 | inWord = true; 229 | wordStart = i; 230 | } 231 | } else if ( inWord && !is_kw( context[i] ) ) { 232 | dohl(i); 233 | } 234 | if ( ( context[i] != '_' ) && ispunct( context[i] ) ) { 235 | wordStart = i; 236 | dohl( i + 1 ); 237 | } 238 | } 239 | if ( inWord ) { 240 | dohl(context.length()); 241 | } 242 | } 243 | 244 | void hook_modify( std::string& currentInput_, int&, Replxx* rx ) { 245 | char prompt[64]; 246 | snprintf( prompt, 64, "\x1b[1;32mreplxx\x1b[0m[%lu]> ", currentInput_.length() ); 247 | rx->set_prompt( prompt ); 248 | } 249 | 250 | Replxx::ACTION_RESULT message( Replxx& replxx, std::string s, char32_t ) { 251 | replxx.invoke( Replxx::ACTION::CLEAR_SELF, 0 ); 252 | replxx.print( "%s\n", s.c_str() ); 253 | replxx.invoke( Replxx::ACTION::REPAINT, 0 ); 254 | return ( Replxx::ACTION_RESULT::CONTINUE ); 255 | } 256 | 257 | int main( int argc_, char** argv_ ) { 258 | // words to be completed 259 | std::vector examples { 260 | ".help", ".history", ".quit", ".exit", ".clear", ".prompt ", 261 | "hello", "world", "db", "data", "drive", "print", "put", 262 | "color_black", "color_red", "color_green", "color_brown", "color_blue", 263 | "color_magenta", "color_cyan", "color_lightgray", "color_gray", 264 | "color_brightred", "color_brightgreen", "color_yellow", "color_brightblue", 265 | "color_brightmagenta", "color_brightcyan", "color_white", 266 | "determinANT", "determiNATION", "deterMINE", "deteRMINISM", "detERMINISTIC", "deTERMINED", 267 | "star", "star_galaxy_cluser_supercluster_observable_universe", 268 | }; 269 | 270 | // highlight specific words 271 | // a regex string, and a color 272 | // the order matters, the last match will take precedence 273 | using cl = Replxx::Color; 274 | keyword_highlight_t word_color { 275 | // single chars 276 | {"`", cl::BRIGHTCYAN}, 277 | {"'", cl::BRIGHTBLUE}, 278 | {"\"", cl::BRIGHTBLUE}, 279 | {"-", cl::BRIGHTBLUE}, 280 | {"+", cl::BRIGHTBLUE}, 281 | {"=", cl::BRIGHTBLUE}, 282 | {"/", cl::BRIGHTBLUE}, 283 | {"*", cl::BRIGHTBLUE}, 284 | {"^", cl::BRIGHTBLUE}, 285 | {".", cl::BRIGHTMAGENTA}, 286 | {"(", cl::BRIGHTMAGENTA}, 287 | {")", cl::BRIGHTMAGENTA}, 288 | {"[", cl::BRIGHTMAGENTA}, 289 | {"]", cl::BRIGHTMAGENTA}, 290 | {"{", cl::BRIGHTMAGENTA}, 291 | {"}", cl::BRIGHTMAGENTA}, 292 | 293 | // color keywords 294 | {"color_black", cl::BLACK}, 295 | {"color_red", cl::RED}, 296 | {"color_green", cl::GREEN}, 297 | {"color_brown", cl::BROWN}, 298 | {"color_blue", cl::BLUE}, 299 | {"color_magenta", cl::MAGENTA}, 300 | {"color_cyan", cl::CYAN}, 301 | {"color_lightgray", cl::LIGHTGRAY}, 302 | {"color_gray", cl::GRAY}, 303 | {"color_brightred", cl::BRIGHTRED}, 304 | {"color_brightgreen", cl::BRIGHTGREEN}, 305 | {"color_yellow", cl::YELLOW}, 306 | {"color_brightblue", cl::BRIGHTBLUE}, 307 | {"color_brightmagenta", cl::BRIGHTMAGENTA}, 308 | {"color_brightcyan", cl::BRIGHTCYAN}, 309 | {"color_white", cl::WHITE}, 310 | 311 | // commands 312 | {"help", cl::BRIGHTMAGENTA}, 313 | {"history", cl::BRIGHTMAGENTA}, 314 | {"quit", cl::BRIGHTMAGENTA}, 315 | {"exit", cl::BRIGHTMAGENTA}, 316 | {"clear", cl::BRIGHTMAGENTA}, 317 | {"prompt", cl::BRIGHTMAGENTA}, 318 | }; 319 | syntax_highlight_t regex_color { 320 | // numbers 321 | {"[\\-|+]{0,1}[0-9]+", cl::YELLOW}, // integers 322 | {"[\\-|+]{0,1}[0-9]*\\.[0-9]+", cl::YELLOW}, // decimals 323 | {"[\\-|+]{0,1}[0-9]+e[\\-|+]{0,1}[0-9]+", cl::YELLOW}, // scientific notation 324 | 325 | // strings 326 | {"\".*?\"", cl::BRIGHTGREEN}, // double quotes 327 | {"\'.*?\'", cl::BRIGHTGREEN}, // single quotes 328 | }; 329 | static int const MAX_LABEL_NAME( 32 ); 330 | char label[MAX_LABEL_NAME]; 331 | for ( int r( 0 ); r < 6; ++ r ) { 332 | for ( int g( 0 ); g < 6; ++ g ) { 333 | for ( int b( 0 ); b < 6; ++ b ) { 334 | snprintf( label, MAX_LABEL_NAME, "rgb%d%d%d", r, g, b ); 335 | word_color.insert( std::make_pair( label, replxx::color::rgb666( r, g, b ) ) ); 336 | for ( int br( 0 ); br < 6; ++ br ) { 337 | for ( int bg( 0 ); bg < 6; ++ bg ) { 338 | for ( int bb( 0 ); bb < 6; ++ bb ) { 339 | snprintf( label, MAX_LABEL_NAME, "fg%d%d%dbg%d%d%d", r, g, b, br, bg, bb ); 340 | word_color.insert( 341 | std::make_pair( 342 | label, 343 | rgb666( r, g, b ) | replxx::color::bg( rgb666( br, bg, bb ) ) 344 | ) 345 | ); 346 | } 347 | } 348 | } 349 | } 350 | } 351 | } 352 | for ( int gs( 0 ); gs < 24; ++ gs ) { 353 | snprintf( label, MAX_LABEL_NAME, "gs%d", gs ); 354 | word_color.insert( std::make_pair( label, grayscale( gs ) ) ); 355 | for ( int bgs( 0 ); bgs < 24; ++ bgs ) { 356 | snprintf( label, MAX_LABEL_NAME, "gs%dgs%d", gs, bgs ); 357 | word_color.insert( std::make_pair( label, grayscale( gs ) | bg( grayscale( bgs ) ) ) ); 358 | } 359 | } 360 | Replxx::Color colorCodes[] = { 361 | Replxx::Color::BLACK, Replxx::Color::RED, Replxx::Color::GREEN, Replxx::Color::BROWN, Replxx::Color::BLUE, 362 | Replxx::Color::CYAN, Replxx::Color::MAGENTA, Replxx::Color::LIGHTGRAY, Replxx::Color::GRAY, Replxx::Color::BRIGHTRED, 363 | Replxx::Color::BRIGHTGREEN, Replxx::Color::YELLOW, Replxx::Color::BRIGHTBLUE, Replxx::Color::BRIGHTCYAN, 364 | Replxx::Color::BRIGHTMAGENTA, Replxx::Color::WHITE 365 | }; 366 | for ( Replxx::Color bg : colorCodes ) { 367 | for ( Replxx::Color fg : colorCodes ) { 368 | snprintf( label, MAX_LABEL_NAME, "c_%d_%d", static_cast( fg ), static_cast( bg ) ); 369 | word_color.insert( std::make_pair( label, fg | replxx::color::bg( bg ) ) ); 370 | } 371 | } 372 | 373 | bool tickMessages( false ); 374 | bool promptFan( false ); 375 | bool promptInCallback( false ); 376 | bool indentMultiline( false ); 377 | bool bracketedPaste( false ); 378 | bool ignoreCase( false ); 379 | std::string keys; 380 | std::string prompt; 381 | int hintDelay( 0 ); 382 | while ( argc_ > 1 ) { 383 | -- argc_; 384 | ++ argv_; 385 | switch ( (*argv_)[0] ) { 386 | case ( 'm' ): tickMessages = true; break; 387 | case ( 'F' ): promptFan = true; break; 388 | case ( 'P' ): promptInCallback = true; break; 389 | case ( 'I' ): indentMultiline = true; break; 390 | case ( 'i' ): ignoreCase = true; break; 391 | case ( 'k' ): keys = (*argv_) + 1; break; 392 | case ( 'd' ): hintDelay = std::stoi( (*argv_) + 1 ); break; 393 | case ( 'h' ): examples.push_back( (*argv_) + 1 ); break; 394 | case ( 'p' ): prompt = (*argv_) + 1; break; 395 | case ( 'B' ): bracketedPaste = true; break; 396 | } 397 | } 398 | 399 | // init the repl 400 | Replxx rx; 401 | Tick tick( rx, keys, tickMessages, promptFan ); 402 | rx.install_window_change_handler(); 403 | 404 | // the path to the history file 405 | std::string history_file_path {"./replxx_history.txt"}; 406 | 407 | // load the history file if it exists 408 | /* scope for ifstream object for auto-close */ { 409 | std::ifstream history_file( history_file_path.c_str() ); 410 | rx.history_load( history_file ); 411 | } 412 | 413 | // set the max history size 414 | rx.set_max_history_size(128); 415 | 416 | // set the max number of hint rows to show 417 | rx.set_max_hint_rows(3); 418 | 419 | // set the callbacks 420 | using namespace std::placeholders; 421 | rx.set_completion_callback( std::bind( &hook_completion, _1, _2, cref( examples ), ignoreCase ) ); 422 | rx.set_highlighter_callback( std::bind( &hook_color, _1, _2, cref( regex_color ), cref( word_color ) ) ); 423 | rx.set_hint_callback( std::bind( &hook_hint, _1, _2, _3, cref( examples ), ignoreCase ) ); 424 | if ( promptInCallback ) { 425 | rx.set_modify_callback( std::bind( &hook_modify, _1, _2, &rx ) ); 426 | } 427 | 428 | // other api calls 429 | rx.set_word_break_characters( " \n\t.,-%!;:=*~^'\"/?<>|[](){}" ); 430 | rx.set_completion_count_cutoff( 128 ); 431 | rx.set_hint_delay( hintDelay ); 432 | rx.set_double_tab_completion( false ); 433 | rx.set_complete_on_empty( true ); 434 | rx.set_beep_on_ambiguous_completion( false ); 435 | rx.set_no_color( false ); 436 | rx.set_indent_multiline( indentMultiline ); 437 | if ( bracketedPaste ) { 438 | rx.enable_bracketed_paste(); 439 | } 440 | rx.set_ignore_case( ignoreCase ); 441 | 442 | // showcase key bindings 443 | rx.bind_key_internal( Replxx::KEY::BACKSPACE, "delete_character_left_of_cursor" ); 444 | rx.bind_key_internal( Replxx::KEY::DELETE, "delete_character_under_cursor" ); 445 | rx.bind_key_internal( Replxx::KEY::LEFT, "move_cursor_left" ); 446 | rx.bind_key_internal( Replxx::KEY::RIGHT, "move_cursor_right" ); 447 | rx.bind_key_internal( Replxx::KEY::UP, "line_previous" ); 448 | rx.bind_key_internal( Replxx::KEY::DOWN, "line_next" ); 449 | rx.bind_key_internal( Replxx::KEY::meta( Replxx::KEY::UP ), "history_previous" ); 450 | rx.bind_key_internal( Replxx::KEY::meta( Replxx::KEY::DOWN ), "history_next" ); 451 | rx.bind_key_internal( Replxx::KEY::PAGE_UP, "history_first" ); 452 | rx.bind_key_internal( Replxx::KEY::PAGE_DOWN, "history_last" ); 453 | rx.bind_key_internal( Replxx::KEY::HOME, "move_cursor_to_begining_of_line" ); 454 | rx.bind_key_internal( Replxx::KEY::END, "move_cursor_to_end_of_line" ); 455 | rx.bind_key_internal( Replxx::KEY::TAB, "complete_line" ); 456 | rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::LEFT ), "move_cursor_one_word_left" ); 457 | rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::RIGHT ), "move_cursor_one_word_right" ); 458 | rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::UP ), "hint_previous" ); 459 | rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::DOWN ), "hint_next" ); 460 | rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::ENTER ), "commit_line" ); 461 | rx.bind_key_internal( Replxx::KEY::control( 'R' ), "history_incremental_search" ); 462 | rx.bind_key_internal( Replxx::KEY::control( 'W' ), "kill_to_begining_of_word" ); 463 | rx.bind_key_internal( Replxx::KEY::control( 'U' ), "kill_to_begining_of_line" ); 464 | rx.bind_key_internal( Replxx::KEY::control( 'K' ), "kill_to_end_of_line" ); 465 | rx.bind_key_internal( Replxx::KEY::control( 'Y' ), "yank" ); 466 | rx.bind_key_internal( Replxx::KEY::control( 'L' ), "clear_screen" ); 467 | rx.bind_key_internal( Replxx::KEY::control( 'D' ), "send_eof" ); 468 | rx.bind_key_internal( Replxx::KEY::control( 'C' ), "abort_line" ); 469 | rx.bind_key_internal( Replxx::KEY::control( 'T' ), "transpose_characters" ); 470 | #ifndef _WIN32 471 | rx.bind_key_internal( Replxx::KEY::control( 'V' ), "verbatim_insert" ); 472 | rx.bind_key_internal( Replxx::KEY::control( 'Z' ), "suspend" ); 473 | #endif 474 | rx.bind_key_internal( Replxx::KEY::meta( Replxx::KEY::BACKSPACE ), "kill_to_whitespace_on_left" ); 475 | rx.bind_key_internal( Replxx::KEY::meta( 'p' ), "history_common_prefix_search" ); 476 | rx.bind_key_internal( Replxx::KEY::meta( 'n' ), "history_common_prefix_search" ); 477 | rx.bind_key_internal( Replxx::KEY::meta( 'd' ), "kill_to_end_of_word" ); 478 | rx.bind_key_internal( Replxx::KEY::meta( 'y' ), "yank_cycle" ); 479 | rx.bind_key_internal( Replxx::KEY::meta( 'u' ), "uppercase_word" ); 480 | rx.bind_key_internal( Replxx::KEY::meta( 'l' ), "lowercase_word" ); 481 | rx.bind_key_internal( Replxx::KEY::meta( 'c' ), "capitalize_word" ); 482 | rx.bind_key_internal( 'a', "insert_character" ); 483 | rx.bind_key_internal( Replxx::KEY::INSERT, "toggle_overwrite_mode" ); 484 | rx.bind_key( Replxx::KEY::F1, std::bind( &message, std::ref( rx ), "", _1 ) ); 485 | rx.bind_key( Replxx::KEY::F2, std::bind( &message, std::ref( rx ), "", _1 ) ); 486 | rx.bind_key( Replxx::KEY::F3, std::bind( &message, std::ref( rx ), "", _1 ) ); 487 | rx.bind_key( Replxx::KEY::F4, std::bind( &message, std::ref( rx ), "", _1 ) ); 488 | rx.bind_key( Replxx::KEY::F5, std::bind( &message, std::ref( rx ), "", _1 ) ); 489 | rx.bind_key( Replxx::KEY::F6, std::bind( &message, std::ref( rx ), "", _1 ) ); 490 | rx.bind_key( Replxx::KEY::F7, std::bind( &message, std::ref( rx ), "", _1 ) ); 491 | rx.bind_key( Replxx::KEY::F8, std::bind( &message, std::ref( rx ), "", _1 ) ); 492 | rx.bind_key( Replxx::KEY::F9, std::bind( &message, std::ref( rx ), "", _1 ) ); 493 | rx.bind_key( Replxx::KEY::F10, std::bind( &message, std::ref( rx ), "", _1 ) ); 494 | rx.bind_key( Replxx::KEY::F11, std::bind( &message, std::ref( rx ), "", _1 ) ); 495 | rx.bind_key( Replxx::KEY::F12, std::bind( &message, std::ref( rx ), "", _1 ) ); 496 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F1 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 497 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F2 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 498 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F3 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 499 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F4 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 500 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F5 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 501 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F6 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 502 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F7 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 503 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F8 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 504 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F9 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 505 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F10 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 506 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F11 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 507 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F12 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 508 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::F1 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 509 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::F2 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 510 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::F3 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 511 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::F4 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 512 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::F5 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 513 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::F6 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 514 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::F7 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 515 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::F8 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 516 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::F9 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 517 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::F10 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 518 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::F11 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 519 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::F12 ), std::bind( &message, std::ref( rx ), "", _1 ) ); 520 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::TAB ), std::bind( &message, std::ref( rx ), "", _1 ) ); 521 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::HOME ), std::bind( &message, std::ref( rx ), "", _1 ) ); 522 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::HOME ), std::bind( &message, std::ref( rx ), "", _1 ) ); 523 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::END ), std::bind( &message, std::ref( rx ), "", _1 ) ); 524 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::END ), std::bind( &message, std::ref( rx ), "", _1 ) ); 525 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::PAGE_UP ), std::bind( &message, std::ref( rx ), "", _1 ) ); 526 | rx.bind_key( Replxx::KEY::control( Replxx::KEY::PAGE_DOWN ), std::bind( &message, std::ref( rx ), "", _1 ) ); 527 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::LEFT ), std::bind( &message, std::ref( rx ), "", _1 ) ); 528 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::RIGHT ), std::bind( &message, std::ref( rx ), "", _1 ) ); 529 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::UP ), std::bind( &message, std::ref( rx ), "", _1 ) ); 530 | rx.bind_key( Replxx::KEY::shift( Replxx::KEY::DOWN ), std::bind( &message, std::ref( rx ), "", _1 ) ); 531 | rx.bind_key( Replxx::KEY::meta( '\r' ), std::bind( &message, std::ref( rx ), "", _1 ) ); 532 | 533 | // display initial welcome message 534 | std::cout 535 | << "Welcome to Replxx\n" 536 | << "Press 'tab' to view autocompletions\n" 537 | << "Type '.help' for help\n" 538 | << "Type '.quit' or '.exit' to exit\n\n"; 539 | 540 | // set the repl prompt 541 | if ( prompt.empty() ) { 542 | prompt = "\x1b[1;32mreplxx\x1b[0m> "; 543 | } 544 | 545 | // main repl loop 546 | if ( ! keys.empty() || tickMessages || promptFan ) { 547 | tick.start(); 548 | } 549 | for (;;) { 550 | // display the prompt and retrieve input from the user 551 | char const* cinput{ nullptr }; 552 | 553 | do { 554 | cinput = rx.input(prompt); 555 | } while ( ( cinput == nullptr ) && ( errno == EAGAIN ) ); 556 | 557 | if (cinput == nullptr) { 558 | break; 559 | } 560 | 561 | // change cinput into a std::string 562 | // easier to manipulate 563 | std::string input {cinput}; 564 | 565 | if (input.empty()) { 566 | // user hit enter on an empty line 567 | 568 | continue; 569 | 570 | } else if (input.compare(0, 5, ".quit") == 0 || input.compare(0, 5, ".exit") == 0) { 571 | // exit the repl 572 | 573 | rx.history_add(input); 574 | break; 575 | 576 | } else if (input.compare(0, 5, ".help") == 0) { 577 | // display the help output 578 | std::cout 579 | << ".help\n\tdisplays the help output\n" 580 | << ".quit\n\texit the repl\n" 581 | << ".exit\n\texit the repl\n" 582 | << ".clear\n\tclears the screen\n" 583 | << ".history\n\tdisplays the history output\n" 584 | << ".prompt \n\tset the repl prompt to \n"; 585 | 586 | rx.history_add(input); 587 | continue; 588 | 589 | } else if (input.compare(0, 7, ".prompt") == 0) { 590 | // set the repl prompt text 591 | auto pos = input.find(" "); 592 | if (pos == std::string::npos) { 593 | std::cout << "Error: '.prompt' missing argument\n"; 594 | } else { 595 | prompt = input.substr(pos + 1) + " "; 596 | } 597 | 598 | rx.history_add(input); 599 | continue; 600 | 601 | } else if (input.compare(0, 8, ".history") == 0) { 602 | // display the current history 603 | Replxx::HistoryScan hs( rx.history_scan() ); 604 | for ( int i( 0 ); hs.next(); ++ i ) { 605 | std::cout << std::setw(4) << i << ": " << hs.get().text() << "\n"; 606 | } 607 | 608 | rx.history_add(input); 609 | continue; 610 | 611 | } else if (input.compare(0, 6, ".merge") == 0) { 612 | history_file_path = "replxx_history_alt.txt"; 613 | 614 | rx.history_add(input); 615 | continue; 616 | 617 | } else if (input.compare(0, 5, ".save") == 0) { 618 | history_file_path = "replxx_history_alt.txt"; 619 | std::ofstream history_file( history_file_path.c_str() ); 620 | rx.history_save( history_file ); 621 | continue; 622 | 623 | } else if (input.compare(0, 6, ".clear") == 0) { 624 | // clear the screen 625 | rx.clear_screen(); 626 | 627 | rx.history_add(input); 628 | continue; 629 | 630 | } else { 631 | // default action 632 | // echo the input 633 | 634 | rx.print( "%s\n", input.c_str() ); 635 | 636 | rx.history_add( input ); 637 | continue; 638 | } 639 | } 640 | if ( ! keys.empty() || tickMessages || promptFan ) { 641 | tick.stop(); 642 | } 643 | 644 | // save the history 645 | rx.history_sync( history_file_path ); 646 | if ( bracketedPaste ) { 647 | rx.disable_bracketed_paste(); 648 | } 649 | if ( bracketedPaste || promptInCallback || promptFan ) { 650 | std::cout << "\n" << prompt; 651 | } 652 | 653 | std::cout << "\nExiting Replxx\n"; 654 | 655 | return 0; 656 | } 657 | --------------------------------------------------------------------------------