Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

replxx_impl.cxx 69KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984
  1. #include <algorithm>
  2. #include <memory>
  3. #include <cerrno>
  4. #include <iostream>
  5. #ifdef _WIN32
  6. #include <windows.h>
  7. #include <io.h>
  8. #if _MSC_VER < 1900
  9. #define snprintf _snprintf // Microsoft headers use underscores in some names
  10. #endif
  11. #define strcasecmp _stricmp
  12. #define write _write
  13. #define STDIN_FILENO 0
  14. #else /* _WIN32 */
  15. #include <unistd.h>
  16. #include <signal.h>
  17. #endif /* _WIN32 */
  18. #ifdef _WIN32
  19. #include "windows.hxx"
  20. #endif
  21. #include "replxx_impl.hxx"
  22. #include "utf8string.hxx"
  23. #include "prompt.hxx"
  24. #include "util.hxx"
  25. #include "io.hxx"
  26. #include "history.hxx"
  27. #include "replxx.hxx"
  28. using namespace std;
  29. namespace replxx {
  30. #ifndef _WIN32
  31. bool gotResize = false;
  32. #endif
  33. namespace {
  34. static int const REPLXX_MAX_HINT_ROWS( 4 );
  35. /*
  36. * All whitespaces and all non-alphanumerical characters from ASCII range
  37. * with an exception of an underscore ('_').
  38. */
  39. char const defaultBreakChars[] = " \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?";
  40. #ifndef _WIN32
  41. static void WindowSizeChanged(int) {
  42. // do nothing here but setting this flag
  43. gotResize = true;
  44. }
  45. #endif
  46. static const char* unsupported_term[] = {"dumb", "cons25", "emacs", NULL};
  47. static bool isUnsupportedTerm(void) {
  48. char* term = getenv("TERM");
  49. if (term == NULL) {
  50. return false;
  51. }
  52. for (int j = 0; unsupported_term[j]; ++j) {
  53. if (!strcasecmp(term, unsupported_term[j])) {
  54. return true;
  55. }
  56. }
  57. return false;
  58. }
  59. }
  60. Replxx::ReplxxImpl::ReplxxImpl( FILE*, FILE*, FILE* )
  61. : _utf8Buffer()
  62. , _data()
  63. , _charWidths()
  64. , _display()
  65. , _displayInputLength( 0 )
  66. , _hint()
  67. , _pos( 0 )
  68. , _prefix( 0 )
  69. , _hintSelection( -1 )
  70. , _history()
  71. , _killRing()
  72. , _maxHintRows( REPLXX_MAX_HINT_ROWS )
  73. , _hintDelay( 0 )
  74. , _breakChars( defaultBreakChars )
  75. , _completionCountCutoff( 100 )
  76. , _overwrite( false )
  77. , _doubleTabCompletion( false )
  78. , _completeOnEmpty( true )
  79. , _beepOnAmbiguousCompletion( false )
  80. , _noColor( false )
  81. , _keyPressHandlers()
  82. , _terminal()
  83. , _currentThread()
  84. , _prompt( _terminal )
  85. , _completionCallback( nullptr )
  86. , _highlighterCallback( nullptr )
  87. , _hintCallback( nullptr )
  88. , _keyPresses()
  89. , _messages()
  90. , _completions()
  91. , _completionContextLength( 0 )
  92. , _completionSelection( -1 )
  93. , _preloadedBuffer()
  94. , _errorMessage()
  95. , _modifiedState( false )
  96. , _mutex() {
  97. using namespace std::placeholders;
  98. bind_key( Replxx::KEY::control( 'A' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE, _1 ) );
  99. bind_key( Replxx::KEY::HOME + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE, _1 ) );
  100. bind_key( Replxx::KEY::control( 'E' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE, _1 ) );
  101. bind_key( Replxx::KEY::END + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE, _1 ) );
  102. bind_key( Replxx::KEY::control( 'B' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_LEFT, _1 ) );
  103. bind_key( Replxx::KEY::LEFT + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_LEFT, _1 ) );
  104. bind_key( Replxx::KEY::control( 'F' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_RIGHT, _1 ) );
  105. bind_key( Replxx::KEY::RIGHT + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_RIGHT, _1 ) );
  106. bind_key( Replxx::KEY::meta( 'b' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT, _1 ) );
  107. bind_key( Replxx::KEY::meta( 'B' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT, _1 ) );
  108. bind_key( Replxx::KEY::control( Replxx::KEY::LEFT ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT, _1 ) );
  109. bind_key( Replxx::KEY::meta( Replxx::KEY::LEFT ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT, _1 ) ); // Emacs allows Meta, readline don't
  110. bind_key( Replxx::KEY::meta( 'f' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT, _1 ) );
  111. bind_key( Replxx::KEY::meta( 'F' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT, _1 ) );
  112. bind_key( Replxx::KEY::control( Replxx::KEY::RIGHT ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT, _1 ) );
  113. bind_key( Replxx::KEY::meta( Replxx::KEY::RIGHT ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT, _1 ) ); // Emacs allows Meta, readline don't
  114. bind_key( Replxx::KEY::meta( Replxx::KEY::BACKSPACE ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT, _1 ) );
  115. bind_key( Replxx::KEY::meta( 'd' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_WORD, _1 ) );
  116. bind_key( Replxx::KEY::meta( 'D' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_WORD, _1 ) );
  117. bind_key( Replxx::KEY::control( 'W' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_WORD, _1 ) );
  118. bind_key( Replxx::KEY::control( 'U' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_LINE, _1 ) );
  119. bind_key( Replxx::KEY::control( 'K' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_LINE, _1 ) );
  120. bind_key( Replxx::KEY::control( 'Y' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK, _1 ) );
  121. bind_key( Replxx::KEY::meta( 'y' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_CYCLE, _1 ) );
  122. bind_key( Replxx::KEY::meta( 'Y' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_CYCLE, _1 ) );
  123. bind_key( Replxx::KEY::meta( 'c' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_WORD, _1 ) );
  124. bind_key( Replxx::KEY::meta( 'C' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_WORD, _1 ) );
  125. bind_key( Replxx::KEY::meta( 'l' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_WORD, _1 ) );
  126. bind_key( Replxx::KEY::meta( 'L' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_WORD, _1 ) );
  127. bind_key( Replxx::KEY::meta( 'u' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_WORD, _1 ) );
  128. bind_key( Replxx::KEY::meta( 'U' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_WORD, _1 ) );
  129. bind_key( Replxx::KEY::control( 'T' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TRANSPOSE_CHARACTERS, _1 ) );
  130. bind_key( Replxx::KEY::control( 'C' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::ABORT_LINE, _1 ) );
  131. bind_key( Replxx::KEY::control( 'D' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SEND_EOF, _1 ) );
  132. bind_key( Replxx::KEY::INSERT + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TOGGLE_OVERWRITE_MODE, _1 ) );
  133. bind_key( 127, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR, _1 ) );
  134. bind_key( Replxx::KEY::DELETE + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR, _1 ) );
  135. bind_key( Replxx::KEY::BACKSPACE + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR, _1 ) );
  136. bind_key( Replxx::KEY::control( 'J' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMMIT_LINE, _1 ) );
  137. bind_key( Replxx::KEY::ENTER + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMMIT_LINE, _1 ) );
  138. bind_key( Replxx::KEY::control( 'L' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CLEAR_SCREEN, _1 ) );
  139. bind_key( Replxx::KEY::control( 'N' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_NEXT, _1 ) );
  140. bind_key( Replxx::KEY::control( 'P' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_PREVIOUS, _1 ) );
  141. bind_key( Replxx::KEY::DOWN + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_NEXT, _1 ) );
  142. bind_key( Replxx::KEY::UP + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_PREVIOUS, _1 ) );
  143. bind_key( Replxx::KEY::meta( '>' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_LAST, _1 ) );
  144. bind_key( Replxx::KEY::meta( '<' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_FIRST, _1 ) );
  145. bind_key( Replxx::KEY::PAGE_DOWN + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_LAST, _1 ) );
  146. bind_key( Replxx::KEY::PAGE_UP + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_FIRST, _1 ) );
  147. bind_key( Replxx::KEY::control( Replxx::KEY::UP ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_PREVIOUS, _1 ) );
  148. bind_key( Replxx::KEY::control( Replxx::KEY::DOWN ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_NEXT, _1 ) );
  149. #ifndef _WIN32
  150. bind_key( Replxx::KEY::control( 'V' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::VERBATIM_INSERT, _1 ) );
  151. bind_key( Replxx::KEY::control( 'Z' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SUSPEND, _1 ) );
  152. #endif
  153. bind_key( Replxx::KEY::TAB + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_LINE, _1 ) );
  154. bind_key( Replxx::KEY::control( 'R' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH, _1 ) );
  155. bind_key( Replxx::KEY::control( 'S' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH, _1 ) );
  156. bind_key( Replxx::KEY::meta( 'p' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH, _1 ) );
  157. bind_key( Replxx::KEY::meta( 'P' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH, _1 ) );
  158. bind_key( Replxx::KEY::meta( 'n' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH, _1 ) );
  159. bind_key( Replxx::KEY::meta( 'N' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH, _1 ) );
  160. }
  161. Replxx::ACTION_RESULT Replxx::ReplxxImpl::invoke( Replxx::ACTION action_, char32_t code ) {
  162. switch ( action_ ) {
  163. case ( Replxx::ACTION::INSERT_CHARACTER ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::insert_character, code ) );
  164. case ( Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::delete_character, code ) );
  165. case ( Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::backspace_character, code ) );
  166. case ( Replxx::ACTION::KILL_TO_END_OF_LINE ): return ( action( WANT_REFRESH | SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_to_end_of_line, code ) );
  167. case ( Replxx::ACTION::KILL_TO_BEGINING_OF_LINE ): return ( action( SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_to_begining_of_line, code ) );
  168. case ( Replxx::ACTION::KILL_TO_END_OF_WORD ): return ( action( SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_word_to_right, code ) );
  169. case ( Replxx::ACTION::KILL_TO_BEGINING_OF_WORD ): return ( action( SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_word_to_left, code ) );
  170. case ( Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT ): return ( action( SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_to_whitespace_to_left, code ) );
  171. case ( Replxx::ACTION::YANK ): return ( action( NOOP, &Replxx::ReplxxImpl::yank, code ) );
  172. case ( Replxx::ACTION::YANK_CYCLE ): return ( action( NOOP, &Replxx::ReplxxImpl::yank_cycle, code ) );
  173. case ( Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE ): return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::go_to_begining_of_line, code ) );
  174. case ( Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE ): return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::go_to_end_of_line, code ) );
  175. case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_left, code ) );
  176. case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right, code ) );
  177. case ( Replxx::ACTION::MOVE_CURSOR_LEFT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_char_left, code ) );
  178. case ( Replxx::ACTION::MOVE_CURSOR_RIGHT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_char_right, code ) );
  179. case ( Replxx::ACTION::HISTORY_NEXT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_next, code ) );
  180. case ( Replxx::ACTION::HISTORY_PREVIOUS ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_previous, code ) );
  181. case ( Replxx::ACTION::HISTORY_FIRST ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_first, code ) );
  182. case ( Replxx::ACTION::HISTORY_LAST ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_last, code ) );
  183. case ( Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH ): return ( action( NOOP, &Replxx::ReplxxImpl::incremental_history_search, code ) );
  184. case ( Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH ): return ( action( RESET_KILL_ACTION | DONT_RESET_PREFIX, &Replxx::ReplxxImpl::common_prefix_search, code ) );
  185. case ( Replxx::ACTION::HINT_NEXT ): return ( action( NOOP, &Replxx::ReplxxImpl::hint_next, code ) );
  186. case ( Replxx::ACTION::HINT_PREVIOUS ): return ( action( NOOP, &Replxx::ReplxxImpl::hint_previous, code ) );
  187. case ( Replxx::ACTION::CAPITALIZE_WORD ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::capitalize_word, code ) );
  188. case ( Replxx::ACTION::LOWERCASE_WORD ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::lowercase_word, code ) );
  189. case ( Replxx::ACTION::UPPERCASE_WORD ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::uppercase_word, code ) );
  190. case ( Replxx::ACTION::TRANSPOSE_CHARACTERS ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::transpose_characters, code ) );
  191. case ( Replxx::ACTION::TOGGLE_OVERWRITE_MODE ): return ( action( NOOP, &Replxx::ReplxxImpl::toggle_overwrite_mode, code ) );
  192. #ifndef _WIN32
  193. case ( Replxx::ACTION::VERBATIM_INSERT ): return ( action( WANT_REFRESH | RESET_KILL_ACTION, &Replxx::ReplxxImpl::verbatim_insert, code ) );
  194. case ( Replxx::ACTION::SUSPEND ): return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::suspend, code ) );
  195. #endif
  196. case ( Replxx::ACTION::CLEAR_SCREEN ): return ( action( NOOP, &Replxx::ReplxxImpl::clear_screen, code ) );
  197. case ( Replxx::ACTION::CLEAR_SELF ): clear_self_to_end_of_screen(); return ( Replxx::ACTION_RESULT::CONTINUE );
  198. case ( Replxx::ACTION::REPAINT ): repaint(); return ( Replxx::ACTION_RESULT::CONTINUE );
  199. case ( Replxx::ACTION::COMPLETE_LINE ): return ( action( NOOP, &Replxx::ReplxxImpl::complete_line, code ) );
  200. case ( Replxx::ACTION::COMPLETE_NEXT ): return ( action( DONT_RESET_COMPLETIONS, &Replxx::ReplxxImpl::complete_next, code ) );
  201. case ( Replxx::ACTION::COMPLETE_PREVIOUS ): return ( action( DONT_RESET_COMPLETIONS, &Replxx::ReplxxImpl::complete_previous, code ) );
  202. case ( Replxx::ACTION::COMMIT_LINE ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::commit_line, code ) );
  203. case ( Replxx::ACTION::ABORT_LINE ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::abort_line, code ) );
  204. case ( Replxx::ACTION::SEND_EOF ): return ( action( NOOP, &Replxx::ReplxxImpl::send_eof, code ) );
  205. }
  206. return ( Replxx::ACTION_RESULT::BAIL );
  207. }
  208. void Replxx::ReplxxImpl::bind_key( char32_t code_, Replxx::key_press_handler_t handler_ ) {
  209. _keyPressHandlers[code_] = handler_;
  210. }
  211. Replxx::State Replxx::ReplxxImpl::get_state( void ) const {
  212. _utf8Buffer.assign( _data );
  213. return ( Replxx::State( _utf8Buffer.get(), _pos ) );
  214. }
  215. void Replxx::ReplxxImpl::set_state( Replxx::State const& state_ ) {
  216. _data.assign( state_.text() );
  217. if ( state_.cursor_position() >= 0 ) {
  218. _pos = min( state_.cursor_position(), _data.length() );
  219. }
  220. _modifiedState = true;
  221. }
  222. char32_t Replxx::ReplxxImpl::read_char( HINT_ACTION hintAction_ ) {
  223. /* try scheduled key presses */ {
  224. std::lock_guard<std::mutex> l( _mutex );
  225. if ( !_keyPresses.empty() ) {
  226. char32_t keyPress( _keyPresses.front() );
  227. _keyPresses.pop_front();
  228. return ( keyPress );
  229. }
  230. }
  231. int hintDelay( hintAction_ != HINT_ACTION::SKIP ? _hintDelay : 0 );
  232. while ( true ) {
  233. Terminal::EVENT_TYPE eventType( _terminal.wait_for_input( hintDelay ) );
  234. if ( eventType == Terminal::EVENT_TYPE::TIMEOUT ) {
  235. refresh_line( HINT_ACTION::REPAINT );
  236. hintDelay = 0;
  237. continue;
  238. }
  239. if ( eventType == Terminal::EVENT_TYPE::KEY_PRESS ) {
  240. break;
  241. }
  242. std::lock_guard<std::mutex> l( _mutex );
  243. clear_self_to_end_of_screen();
  244. while ( ! _messages.empty() ) {
  245. string const& message( _messages.front() );
  246. _terminal.write8( message.data(), message.length() );
  247. _messages.pop_front();
  248. }
  249. repaint();
  250. }
  251. /* try scheduled key presses */ {
  252. std::lock_guard<std::mutex> l( _mutex );
  253. if ( !_keyPresses.empty() ) {
  254. char32_t keyPress( _keyPresses.front() );
  255. _keyPresses.pop_front();
  256. return ( keyPress );
  257. }
  258. }
  259. return ( _terminal.read_char() );
  260. }
  261. void Replxx::ReplxxImpl::clear( void ) {
  262. _pos = 0;
  263. _prefix = 0;
  264. _completions.clear();
  265. _completionContextLength = 0;
  266. _completionSelection = -1;
  267. _data.clear();
  268. _hintSelection = -1;
  269. _hint = UnicodeString();
  270. _display.clear();
  271. _displayInputLength = 0;
  272. }
  273. Replxx::ReplxxImpl::completions_t Replxx::ReplxxImpl::call_completer( std::string const& input, int& contextLen_ ) const {
  274. Replxx::completions_t completionsIntermediary(
  275. !! _completionCallback
  276. ? _completionCallback( input, contextLen_ )
  277. : Replxx::completions_t()
  278. );
  279. completions_t completions;
  280. completions.reserve( completionsIntermediary.size() );
  281. for ( Replxx::Completion const& c : completionsIntermediary ) {
  282. completions.emplace_back( c );
  283. }
  284. return ( completions );
  285. }
  286. Replxx::ReplxxImpl::hints_t Replxx::ReplxxImpl::call_hinter( std::string const& input, int& contextLen, Replxx::Color& color ) const {
  287. Replxx::hints_t hintsIntermediary(
  288. !! _hintCallback
  289. ? _hintCallback( input, contextLen, color )
  290. : Replxx::hints_t()
  291. );
  292. hints_t hints;
  293. hints.reserve( hintsIntermediary.size() );
  294. for ( std::string const& h : hintsIntermediary ) {
  295. hints.emplace_back( h.c_str() );
  296. }
  297. return ( hints );
  298. }
  299. void Replxx::ReplxxImpl::set_preload_buffer( std::string const& preloadText ) {
  300. _preloadedBuffer = preloadText;
  301. // remove characters that won't display correctly
  302. bool controlsStripped = false;
  303. int whitespaceSeen( 0 );
  304. for ( std::string::iterator it( _preloadedBuffer.begin() ); it != _preloadedBuffer.end(); ) {
  305. unsigned char c = *it;
  306. if ( '\r' == c ) { // silently skip CR
  307. _preloadedBuffer.erase( it, it + 1 );
  308. continue;
  309. }
  310. if ( ( '\n' == c ) || ( '\t' == c ) ) { // note newline or tab
  311. ++ whitespaceSeen;
  312. ++ it;
  313. continue;
  314. }
  315. if ( whitespaceSeen > 0 ) {
  316. it -= whitespaceSeen;
  317. *it = ' ';
  318. _preloadedBuffer.erase( it + 1, it + whitespaceSeen - 1 );
  319. }
  320. if ( is_control_code( c ) ) { // remove other control characters, flag for message
  321. controlsStripped = true;
  322. if ( whitespaceSeen > 0 ) {
  323. _preloadedBuffer.erase( it, it + 1 );
  324. -- it;
  325. } else {
  326. *it = ' ';
  327. }
  328. }
  329. whitespaceSeen = 0;
  330. ++ it;
  331. }
  332. if ( whitespaceSeen > 0 ) {
  333. std::string::iterator it = _preloadedBuffer.end() - whitespaceSeen;
  334. *it = ' ';
  335. if ( whitespaceSeen > 1 ) {
  336. _preloadedBuffer.erase( it + 1, _preloadedBuffer.end() );
  337. }
  338. }
  339. _errorMessage.clear();
  340. if ( controlsStripped ) {
  341. _errorMessage.assign( " [Edited line: control characters were converted to spaces]\n" );
  342. }
  343. }
  344. char const* Replxx::ReplxxImpl::read_from_stdin( void ) {
  345. if ( _preloadedBuffer.empty() ) {
  346. getline( cin, _preloadedBuffer );
  347. if ( ! cin.good() ) {
  348. return nullptr;
  349. }
  350. }
  351. while ( ! _preloadedBuffer.empty() && ( ( _preloadedBuffer.back() == '\r' ) || ( _preloadedBuffer.back() == '\n' ) ) ) {
  352. _preloadedBuffer.pop_back();
  353. }
  354. _utf8Buffer.assign( _preloadedBuffer );
  355. _preloadedBuffer.clear();
  356. return _utf8Buffer.get();
  357. }
  358. void Replxx::ReplxxImpl::emulate_key_press( char32_t keyCode_ ) {
  359. std::lock_guard<std::mutex> l( _mutex );
  360. _keyPresses.push_back( keyCode_ );
  361. if ( ( _currentThread != std::thread::id() ) && ( _currentThread != std::this_thread::get_id() ) ) {
  362. _terminal.notify_event( Terminal::EVENT_TYPE::KEY_PRESS );
  363. }
  364. }
  365. char const* Replxx::ReplxxImpl::input( std::string const& prompt ) {
  366. #ifndef _WIN32
  367. gotResize = false;
  368. #endif
  369. try {
  370. errno = 0;
  371. if ( ! tty::in ) { // input not from a terminal, we should work with piped input, i.e. redirected stdin
  372. return ( read_from_stdin() );
  373. }
  374. if (!_errorMessage.empty()) {
  375. printf("%s", _errorMessage.c_str());
  376. fflush(stdout);
  377. _errorMessage.clear();
  378. }
  379. if ( isUnsupportedTerm() ) {
  380. cout << prompt << flush;
  381. fflush(stdout);
  382. return ( read_from_stdin() );
  383. }
  384. if (_terminal.enable_raw_mode() == -1) {
  385. return nullptr;
  386. }
  387. _prompt.set_text( UnicodeString( prompt ) );
  388. _currentThread = std::this_thread::get_id();
  389. clear();
  390. if (!_preloadedBuffer.empty()) {
  391. preload_puffer(_preloadedBuffer.c_str());
  392. _preloadedBuffer.clear();
  393. }
  394. if ( get_input_line() == -1 ) {
  395. return ( finalize_input( nullptr ) );
  396. }
  397. printf("\n");
  398. _utf8Buffer.assign( _data );
  399. return ( finalize_input( _utf8Buffer.get() ) );
  400. } catch ( std::exception const& ) {
  401. return ( finalize_input( nullptr ) );
  402. }
  403. }
  404. char const* Replxx::ReplxxImpl::finalize_input( char const* retVal_ ) {
  405. _currentThread = std::thread::id();
  406. _terminal.disable_raw_mode();
  407. return ( retVal_ );
  408. }
  409. int Replxx::ReplxxImpl::install_window_change_handler( void ) {
  410. #ifndef _WIN32
  411. struct sigaction sa;
  412. sigemptyset(&sa.sa_mask);
  413. sa.sa_flags = 0;
  414. sa.sa_handler = &WindowSizeChanged;
  415. if (sigaction(SIGWINCH, &sa, nullptr) == -1) {
  416. return errno;
  417. }
  418. #endif
  419. return 0;
  420. }
  421. void Replxx::ReplxxImpl::print( char const* str_, int size_ ) {
  422. if ( ( _currentThread == std::thread::id() ) || ( _currentThread == std::this_thread::get_id() ) ) {
  423. _terminal.write8( str_, size_ );
  424. } else {
  425. std::lock_guard<std::mutex> l( _mutex );
  426. _messages.emplace_back( str_, size_ );
  427. _terminal.notify_event( Terminal::EVENT_TYPE::MESSAGE );
  428. }
  429. return;
  430. }
  431. void Replxx::ReplxxImpl::preload_puffer(const char* preloadText) {
  432. _data.assign( preloadText );
  433. _charWidths.resize( _data.length() );
  434. recompute_character_widths( _data.get(), _charWidths.data(), _data.length() );
  435. _prefix = _pos = _data.length();
  436. }
  437. void Replxx::ReplxxImpl::set_color( Replxx::Color color_ ) {
  438. char const* code( ansi_color( color_ ) );
  439. while ( *code ) {
  440. _display.push_back( *code );
  441. ++ code;
  442. }
  443. }
  444. void Replxx::ReplxxImpl::render( char32_t ch ) {
  445. if ( ch == Replxx::KEY::ESCAPE ) {
  446. _display.push_back( '^' );
  447. _display.push_back( '[' );
  448. } else if ( is_control_code( ch ) ) {
  449. _display.push_back( '^' );
  450. _display.push_back( ch + 0x40 );
  451. } else {
  452. _display.push_back( ch );
  453. }
  454. return;
  455. }
  456. void Replxx::ReplxxImpl::render( HINT_ACTION hintAction_ ) {
  457. if ( hintAction_ == HINT_ACTION::TRIM ) {
  458. _display.erase( _display.begin() + _displayInputLength, _display.end() );
  459. return;
  460. }
  461. if ( hintAction_ == HINT_ACTION::SKIP ) {
  462. return;
  463. }
  464. _display.clear();
  465. if ( _noColor ) {
  466. for ( char32_t ch : _data ) {
  467. render( ch );
  468. }
  469. _displayInputLength = _display.size();
  470. return;
  471. }
  472. Replxx::colors_t colors( _data.length(), Replxx::Color::DEFAULT );
  473. _utf8Buffer.assign( _data );
  474. if ( !! _highlighterCallback ) {
  475. _highlighterCallback( _utf8Buffer.get(), colors );
  476. }
  477. paren_info_t pi( matching_paren() );
  478. if ( pi.index != -1 ) {
  479. colors[pi.index] = pi.error ? Replxx::Color::ERROR : Replxx::Color::BRIGHTRED;
  480. }
  481. Replxx::Color c( Replxx::Color::DEFAULT );
  482. for ( int i( 0 ); i < _data.length(); ++ i ) {
  483. if ( colors[i] != c ) {
  484. c = colors[i];
  485. set_color( c );
  486. }
  487. render( _data[i] );
  488. }
  489. set_color( Replxx::Color::DEFAULT );
  490. _displayInputLength = _display.size();
  491. _modifiedState = false;
  492. return;
  493. }
  494. int Replxx::ReplxxImpl::handle_hints( HINT_ACTION hintAction_ ) {
  495. if ( _noColor ) {
  496. return ( 0 );
  497. }
  498. if ( ! _hintCallback ) {
  499. return ( 0 );
  500. }
  501. if ( ( _hintDelay > 0 ) && ( hintAction_ != HINT_ACTION::REPAINT ) ) {
  502. _hintSelection = -1;
  503. return ( 0 );
  504. }
  505. if ( ( hintAction_ == HINT_ACTION::SKIP ) || ( hintAction_ == HINT_ACTION::TRIM ) ) {
  506. return ( 0 );
  507. }
  508. if ( _pos != _data.length() ) {
  509. return ( 0 );
  510. }
  511. _hint = UnicodeString();
  512. int len( 0 );
  513. if ( hintAction_ == HINT_ACTION::REGENERATE ) {
  514. _hintSelection = -1;
  515. }
  516. Replxx::Color c( Replxx::Color::GRAY );
  517. _utf8Buffer.assign( _data, _pos );
  518. int contextLen( context_length() );
  519. Replxx::ReplxxImpl::hints_t hints( call_hinter( _utf8Buffer.get(), contextLen, c ) );
  520. int hintCount( hints.size() );
  521. if ( hintCount == 1 ) {
  522. _hint = hints.front();
  523. len = _hint.length() - contextLen;
  524. if ( len > 0 ) {
  525. set_color( c );
  526. for ( int i( 0 ); i < len; ++ i ) {
  527. _display.push_back( _hint[i + contextLen] );
  528. }
  529. set_color( Replxx::Color::DEFAULT );
  530. }
  531. } else if ( ( _maxHintRows > 0 ) && ( hintCount > 0 ) ) {
  532. int startCol( _prompt._indentation + _pos - contextLen );
  533. int maxCol( _prompt.screen_columns() );
  534. #ifdef _WIN32
  535. -- maxCol;
  536. #endif
  537. if ( _hintSelection < -1 ) {
  538. _hintSelection = hintCount - 1;
  539. } else if ( _hintSelection >= hintCount ) {
  540. _hintSelection = -1;
  541. }
  542. if ( _hintSelection != -1 ) {
  543. _hint = hints[_hintSelection];
  544. len = min<int>( _hint.length(), maxCol - startCol - _data.length() );
  545. if ( contextLen < len ) {
  546. set_color( c );
  547. for ( int i( contextLen ); i < len; ++ i ) {
  548. _display.push_back( _hint[i] );
  549. }
  550. set_color( Replxx::Color::DEFAULT );
  551. }
  552. }
  553. for ( int hintRow( 0 ); hintRow < min( hintCount, _maxHintRows ); ++ hintRow ) {
  554. #ifdef _WIN32
  555. _display.push_back( '\r' );
  556. #endif
  557. _display.push_back( '\n' );
  558. int col( 0 );
  559. for ( int i( 0 ); ( i < startCol ) && ( col < maxCol ); ++ i, ++ col ) {
  560. _display.push_back( ' ' );
  561. }
  562. set_color( c );
  563. for ( int i( _pos - contextLen ); ( i < _pos ) && ( col < maxCol ); ++ i, ++ col ) {
  564. _display.push_back( _data[i] );
  565. }
  566. int hintNo( hintRow + _hintSelection + 1 );
  567. if ( hintNo == hintCount ) {
  568. continue;
  569. } else if ( hintNo > hintCount ) {
  570. -- hintNo;
  571. }
  572. UnicodeString const& h( hints[hintNo % hintCount] );
  573. for ( int i( contextLen ); ( i < h.length() ) && ( col < maxCol ); ++ i, ++ col ) {
  574. _display.push_back( h[i] );
  575. }
  576. set_color( Replxx::Color::DEFAULT );
  577. }
  578. }
  579. return ( len );
  580. }
  581. Replxx::ReplxxImpl::paren_info_t Replxx::ReplxxImpl::matching_paren( void ) {
  582. if (_pos >= _data.length()) {
  583. return ( paren_info_t{ -1, false } );
  584. }
  585. /* this scans for a brace matching _data[_pos] to highlight */
  586. unsigned char part1, part2;
  587. int scanDirection = 0;
  588. if ( strchr("}])", _data[_pos]) ) {
  589. scanDirection = -1; /* backwards */
  590. if (_data[_pos] == '}') {
  591. part1 = '}'; part2 = '{';
  592. } else if (_data[_pos] == ']') {
  593. part1 = ']'; part2 = '[';
  594. } else {
  595. part1 = ')'; part2 = '(';
  596. }
  597. } else if ( strchr("{[(", _data[_pos]) ) {
  598. scanDirection = 1; /* forwards */
  599. if (_data[_pos] == '{') {
  600. //part1 = '{'; part2 = '}';
  601. part1 = '}'; part2 = '{';
  602. } else if (_data[_pos] == '[') {
  603. //part1 = '['; part2 = ']';
  604. part1 = ']'; part2 = '[';
  605. } else {
  606. //part1 = '('; part2 = ')';
  607. part1 = ')'; part2 = '(';
  608. }
  609. } else {
  610. return ( paren_info_t{ -1, false } );
  611. }
  612. int highlightIdx = -1;
  613. bool indicateError = false;
  614. int unmatched = scanDirection;
  615. int unmatchedOther = 0;
  616. for (int i = _pos + scanDirection; i >= 0 && i < _data.length(); i += scanDirection) {
  617. /* TODO: the right thing when inside a string */
  618. if (strchr("}])", _data[i])) {
  619. if (_data[i] == part1) {
  620. --unmatched;
  621. } else {
  622. --unmatchedOther;
  623. }
  624. } else if (strchr("{[(", _data[i])) {
  625. if (_data[i] == part2) {
  626. ++unmatched;
  627. } else {
  628. ++unmatchedOther;
  629. }
  630. }
  631. if (unmatched == 0) {
  632. highlightIdx = i;
  633. indicateError = (unmatchedOther != 0);
  634. break;
  635. }
  636. }
  637. return ( paren_info_t{ highlightIdx, indicateError } );
  638. }
  639. /**
  640. * Refresh the user's input line: the prompt is already onscreen and is not
  641. * redrawn here screen position
  642. */
  643. void Replxx::ReplxxImpl::refresh_line( HINT_ACTION hintAction_ ) {
  644. // check for a matching brace/bracket/paren, remember its position if found
  645. render( hintAction_ );
  646. int hintLen( handle_hints( hintAction_ ) );
  647. // calculate the position of the end of the input line
  648. int xEndOfInput( 0 ), yEndOfInput( 0 );
  649. calculate_screen_position(
  650. _prompt._indentation, 0, _prompt.screen_columns(),
  651. calculate_displayed_length( _data.get(), _data.length() ) + hintLen,
  652. xEndOfInput, yEndOfInput
  653. );
  654. yEndOfInput += count( _display.begin(), _display.end(), '\n' );
  655. // calculate the desired position of the cursor
  656. int xCursorPos( 0 ), yCursorPos( 0 );
  657. calculate_screen_position(
  658. _prompt._indentation, 0, _prompt.screen_columns(),
  659. calculate_displayed_length( _data.get(), _pos ),
  660. xCursorPos, yCursorPos
  661. );
  662. // position at the end of the prompt, clear to end of previous input
  663. _terminal.jump_cursor(
  664. _prompt._indentation, // 0-based on Win32
  665. -( _prompt._cursorRowOffset - _prompt._extraLines )
  666. );
  667. _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END );
  668. _prompt._previousInputLen = _data.length();
  669. // display the input line
  670. _terminal.write32( _display.data(), _display.size() );
  671. #ifndef _WIN32
  672. // we have to generate our own newline on line wrap
  673. if ( ( xEndOfInput == 0 ) && ( yEndOfInput > 0 ) ) {
  674. _terminal.write8( "\n", 1 );
  675. }
  676. #endif
  677. // position the cursor
  678. _terminal.jump_cursor( xCursorPos, -( yEndOfInput - yCursorPos ) );
  679. _prompt._cursorRowOffset = _prompt._extraLines + yCursorPos; // remember row for next pass
  680. }
  681. int Replxx::ReplxxImpl::context_length() {
  682. int prefixLength = _pos;
  683. while ( prefixLength > 0 ) {
  684. if ( is_word_break_character( _data[prefixLength - 1] ) ) {
  685. break;
  686. }
  687. -- prefixLength;
  688. }
  689. return ( _pos - prefixLength );
  690. }
  691. void Replxx::ReplxxImpl::repaint( void ) {
  692. _prompt.write();
  693. for ( int i( _prompt._extraLines ); i < _prompt._cursorRowOffset; ++ i ) {
  694. _terminal.write8( "\n", 1 );
  695. }
  696. refresh_line( HINT_ACTION::SKIP );
  697. }
  698. void Replxx::ReplxxImpl::clear_self_to_end_of_screen( void ) {
  699. // position at the start of the prompt, clear to end of previous input
  700. _terminal.jump_cursor( 0, -_prompt._cursorRowOffset );
  701. _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END );
  702. return;
  703. }
  704. namespace {
  705. int longest_common_prefix( Replxx::ReplxxImpl::completions_t const& completions ) {
  706. int completionsCount( completions.size() );
  707. if ( completionsCount < 1 ) {
  708. return ( 0 );
  709. }
  710. int longestCommonPrefix( 0 );
  711. UnicodeString const& sample( completions.front().text() );
  712. while ( true ) {
  713. if ( longestCommonPrefix >= sample.length() ) {
  714. return ( longestCommonPrefix );
  715. }
  716. char32_t sc( sample[longestCommonPrefix] );
  717. for ( int i( 1 ); i < completionsCount; ++ i ) {
  718. UnicodeString const& candidate( completions[i].text() );
  719. if ( longestCommonPrefix >= candidate.length() ) {
  720. return ( longestCommonPrefix );
  721. }
  722. char32_t cc( candidate[longestCommonPrefix] );
  723. if ( cc != sc ) {
  724. return ( longestCommonPrefix );
  725. }
  726. }
  727. ++ longestCommonPrefix;
  728. }
  729. }
  730. }
  731. /**
  732. * Handle command completion, using a completionCallback() routine to provide
  733. * possible substitutions
  734. * This routine handles the mechanics of updating the user's input buffer with
  735. * possible replacement of text as the user selects a proposed completion string,
  736. * or cancels the completion attempt.
  737. * @param pi - Prompt struct holding information about the prompt and our
  738. * screen position
  739. */
  740. char32_t Replxx::ReplxxImpl::do_complete_line( bool showCompletions_ ) {
  741. char32_t c = 0;
  742. // completionCallback() expects a parsable entity, so find the previous break
  743. // character and
  744. // extract a copy to parse. we also handle the case where tab is hit while
  745. // not at end-of-line.
  746. _utf8Buffer.assign( _data, _pos );
  747. // get a list of completions
  748. _completionSelection = -1;
  749. _completionContextLength = context_length();
  750. _completions = call_completer( _utf8Buffer.get(), _completionContextLength );
  751. // if no completions, we are done
  752. if ( _completions.empty() ) {
  753. beep();
  754. return 0;
  755. }
  756. // at least one completion
  757. int longestCommonPrefix = 0;
  758. int completionsCount( _completions.size() );
  759. int selectedCompletion( 0 );
  760. if ( _hintSelection != -1 ) {
  761. selectedCompletion = _hintSelection;
  762. completionsCount = 1;
  763. }
  764. if ( completionsCount == 1 ) {
  765. longestCommonPrefix = static_cast<int>( _completions[selectedCompletion].text().length() );
  766. } else {
  767. longestCommonPrefix = longest_common_prefix( _completions );
  768. }
  769. if ( _beepOnAmbiguousCompletion && ( completionsCount != 1 ) ) { // beep if ambiguous
  770. beep();
  771. }
  772. // if we can extend the item, extend it and return to main loop
  773. if ( ( longestCommonPrefix > _completionContextLength ) || ( completionsCount == 1 ) ) {
  774. _pos -= _completionContextLength;
  775. _data.erase( _pos, _completionContextLength );
  776. _data.insert( _pos, _completions[selectedCompletion].text(), 0, longestCommonPrefix );
  777. _pos = _pos + longestCommonPrefix;
  778. _completionContextLength = longestCommonPrefix;
  779. refresh_line();
  780. return 0;
  781. }
  782. if ( ! showCompletions_ ) {
  783. return ( 0 );
  784. }
  785. if ( _doubleTabCompletion ) {
  786. // we can't complete any further, wait for second tab
  787. do {
  788. c = read_char();
  789. } while ( c == static_cast<char32_t>( -1 ) );
  790. // if any character other than tab, pass it to the main loop
  791. if ( c != Replxx::KEY::TAB ) {
  792. return c;
  793. }
  794. }
  795. // we got a second tab, maybe show list of possible completions
  796. bool showCompletions = true;
  797. bool onNewLine = false;
  798. if ( static_cast<int>( _completions.size() ) > _completionCountCutoff ) {
  799. int savePos = _pos; // move cursor to EOL to avoid overwriting the command line
  800. _pos = _data.length();
  801. refresh_line();
  802. _pos = savePos;
  803. printf( "\nDisplay all %u possibilities? (y or n)", static_cast<unsigned int>( _completions.size() ) );
  804. fflush(stdout);
  805. onNewLine = true;
  806. while (c != 'y' && c != 'Y' && c != 'n' && c != 'N' && c != Replxx::KEY::control('C')) {
  807. do {
  808. c = read_char();
  809. } while (c == static_cast<char32_t>(-1));
  810. }
  811. switch (c) {
  812. case 'n':
  813. case 'N':
  814. showCompletions = false;
  815. break;
  816. case Replxx::KEY::control('C'):
  817. showCompletions = false;
  818. // Display the ^C we got
  819. _terminal.write8( "^C", 2 );
  820. c = 0;
  821. break;
  822. }
  823. }
  824. // if showing the list, do it the way readline does it
  825. bool stopList( false );
  826. if ( showCompletions ) {
  827. int longestCompletion( 0 );
  828. for ( size_t j( 0 ); j < _completions.size(); ++ j ) {
  829. int itemLength( static_cast<int>( _completions[j].text().length() ) );
  830. if ( itemLength > longestCompletion ) {
  831. longestCompletion = itemLength;
  832. }
  833. }
  834. longestCompletion += 2;
  835. int columnCount = _prompt.screen_columns() / longestCompletion;
  836. if ( columnCount < 1 ) {
  837. columnCount = 1;
  838. }
  839. if ( ! onNewLine ) { // skip this if we showed "Display all %d possibilities?"
  840. int savePos = _pos; // move cursor to EOL to avoid overwriting the command line
  841. _pos = _data.length();
  842. refresh_line( HINT_ACTION::TRIM );
  843. _pos = savePos;
  844. } else {
  845. _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END );
  846. }
  847. size_t pauseRow = _terminal.get_screen_rows() - 1;
  848. size_t rowCount = (_completions.size() + columnCount - 1) / columnCount;
  849. for (size_t row = 0; row < rowCount; ++row) {
  850. if (row == pauseRow) {
  851. printf("\n--More--");
  852. fflush(stdout);
  853. c = 0;
  854. bool doBeep = false;
  855. while (c != ' ' && c != Replxx::KEY::ENTER && c != 'y' && c != 'Y' &&
  856. c != 'n' && c != 'N' && c != 'q' && c != 'Q' &&
  857. c != Replxx::KEY::control('C')) {
  858. if (doBeep) {
  859. beep();
  860. }
  861. doBeep = true;
  862. do {
  863. c = read_char();
  864. } while (c == static_cast<char32_t>(-1));
  865. }
  866. switch (c) {
  867. case ' ':
  868. case 'y':
  869. case 'Y':
  870. printf("\r \r");
  871. pauseRow += _terminal.get_screen_rows() - 1;
  872. break;
  873. case Replxx::KEY::ENTER:
  874. printf("\r \r");
  875. ++pauseRow;
  876. break;
  877. case 'n':
  878. case 'N':
  879. case 'q':
  880. case 'Q':
  881. printf("\r \r");
  882. stopList = true;
  883. break;
  884. case Replxx::KEY::control('C'):
  885. // Display the ^C we got
  886. _terminal.write8( "^C", 2 );
  887. stopList = true;
  888. break;
  889. }
  890. } else {
  891. printf("\n");
  892. }
  893. if (stopList) {
  894. break;
  895. }
  896. static UnicodeString const res( ansi_color( Replxx::Color::DEFAULT ) );
  897. for (int column = 0; column < columnCount; ++column) {
  898. size_t index = (column * rowCount) + row;
  899. if ( index < _completions.size() ) {
  900. Completion const& c( _completions[index] );
  901. int itemLength = static_cast<int>(c.text().length());
  902. fflush(stdout);
  903. if ( longestCommonPrefix > 0 ) {
  904. static UnicodeString const col( ansi_color( Replxx::Color::BRIGHTMAGENTA ) );
  905. if (!_noColor) {
  906. _terminal.write32(col.get(), col.length());
  907. }
  908. _terminal.write32(&_data[_pos - _completionContextLength], longestCommonPrefix);
  909. if (!_noColor) {
  910. _terminal.write32(res.get(), res.length());
  911. }
  912. }
  913. if ( !_noColor && ( c.color() != Replxx::Color::DEFAULT ) ) {
  914. UnicodeString ac( ansi_color( c.color() ) );
  915. _terminal.write32( ac.get(), ac.length() );
  916. }
  917. _terminal.write32( c.text().get() + longestCommonPrefix, itemLength - longestCommonPrefix );
  918. if ( !_noColor && ( c.color() != Replxx::Color::DEFAULT ) ) {
  919. _terminal.write32( res.get(), res.length() );
  920. }
  921. if ( ((column + 1) * rowCount) + row < _completions.size() ) {
  922. for ( int k( itemLength ); k < longestCompletion; ++k ) {
  923. printf( " " );
  924. }
  925. }
  926. }
  927. }
  928. }
  929. fflush(stdout);
  930. }
  931. // display the prompt on a new line, then redisplay the input buffer
  932. if (!stopList || c == Replxx::KEY::control('C')) {
  933. _terminal.write8( "\n", 1 );
  934. }
  935. _prompt.write();
  936. #ifndef _WIN32
  937. // we have to generate our own newline on line wrap on Linux
  938. if (_prompt._indentation == 0 && _prompt._extraLines > 0) {
  939. _terminal.write8( "\n", 1 );
  940. }
  941. #endif
  942. _prompt._cursorRowOffset = _prompt._extraLines;
  943. refresh_line();
  944. return 0;
  945. }
  946. int Replxx::ReplxxImpl::get_input_line( void ) {
  947. // The latest history entry is always our current buffer
  948. if ( _data.length() > 0 ) {
  949. _history.add( _data );
  950. } else {
  951. _history.add( UnicodeString() );
  952. }
  953. _history.reset_pos();
  954. // display the prompt
  955. _prompt.write();
  956. #ifndef _WIN32
  957. // we have to generate our own newline on line wrap on Linux
  958. if ( ( _prompt._indentation == 0 ) && ( _prompt._extraLines > 0 ) ) {
  959. _terminal.write8( "\n", 1 );
  960. }
  961. #endif
  962. // the cursor starts out at the end of the prompt
  963. _prompt._cursorRowOffset = _prompt._extraLines;
  964. // kill and yank start in "other" mode
  965. _killRing.lastAction = KillRing::actionOther;
  966. // if there is already text in the buffer, display it first
  967. if (_data.length() > 0) {
  968. refresh_line();
  969. }
  970. // loop collecting characters, respond to line editing characters
  971. Replxx::ACTION_RESULT next( Replxx::ACTION_RESULT::CONTINUE );
  972. while ( next == Replxx::ACTION_RESULT::CONTINUE ) {
  973. int c( read_char( HINT_ACTION::REPAINT ) ); // get a new keystroke
  974. #ifndef _WIN32
  975. if (c == 0 && gotResize) {
  976. // caught a window resize event
  977. // now redraw the prompt and line
  978. gotResize = false;
  979. _prompt.update_screen_columns();
  980. // redraw the original prompt with current input
  981. dynamicRefresh( _prompt, _data.get(), _data.length(), _pos );
  982. continue;
  983. }
  984. #endif
  985. if (c == 0) {
  986. return _data.length();
  987. }
  988. if (c == -1) {
  989. refresh_line();
  990. continue;
  991. }
  992. if (c == -2) {
  993. _prompt.write();
  994. refresh_line();
  995. continue;
  996. }
  997. key_press_handlers_t::iterator it( _keyPressHandlers.find( c ) );
  998. if ( it != _keyPressHandlers.end() ) {
  999. next = it->second( c );
  1000. if ( _modifiedState ) {
  1001. refresh_line();
  1002. }
  1003. } else {
  1004. next = action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::insert_character, c );
  1005. }
  1006. }
  1007. return ( next == Replxx::ACTION_RESULT::RETURN ? _data.length() : -1 );
  1008. }
  1009. Replxx::ACTION_RESULT Replxx::ReplxxImpl::action( action_trait_t actionTrait_, key_press_handler_raw_t const& handler_, char32_t code_ ) {
  1010. Replxx::ACTION_RESULT res( ( this->*handler_ )( code_ ) );
  1011. if ( actionTrait_ & RESET_KILL_ACTION ) {
  1012. _killRing.lastAction = KillRing::actionOther;
  1013. }
  1014. if ( actionTrait_ & SET_KILL_ACTION ) {
  1015. _killRing.lastAction = KillRing::actionKill;
  1016. }
  1017. if ( ! ( actionTrait_ & DONT_RESET_PREFIX ) ) {
  1018. _prefix = _pos;
  1019. }
  1020. if ( ! ( actionTrait_ & DONT_RESET_COMPLETIONS ) ) {
  1021. _completions.clear();
  1022. _completionSelection = -1;
  1023. _completionContextLength = 0;
  1024. }
  1025. if ( actionTrait_ & WANT_REFRESH ) {
  1026. _modifiedState = true;
  1027. }
  1028. return ( res );
  1029. }
  1030. Replxx::ACTION_RESULT Replxx::ReplxxImpl::insert_character( char32_t c ) {
  1031. _history.reset_recall_most_recent();
  1032. /*
  1033. * beep on unknown Ctrl and/or Meta keys
  1034. * don't insert control characters
  1035. */
  1036. if ( ( c >= static_cast<int>( Replxx::KEY::BASE ) ) || is_control_code( c ) ) {
  1037. beep();
  1038. return ( Replxx::ACTION_RESULT::CONTINUE );
  1039. }
  1040. if ( ! _overwrite || ( _pos >= _data.length() ) ) {
  1041. _data.insert( _pos, c );
  1042. } else {
  1043. _data[_pos] = c;
  1044. }
  1045. ++ _pos;
  1046. int inputLen = calculate_displayed_length( _data.get(), _data.length() );
  1047. if (
  1048. ( _pos == _data.length() )
  1049. && ( _noColor || ! ( !! _highlighterCallback || !! _hintCallback ) )
  1050. && ( _prompt._indentation + inputLen < _prompt.screen_columns() )
  1051. ) {
  1052. /* Avoid a full assign of the line in the
  1053. * trivial case. */
  1054. if (inputLen > _prompt._previousInputLen) {
  1055. _prompt._previousInputLen = inputLen;
  1056. }
  1057. render( c );
  1058. _displayInputLength = _display.size();
  1059. _terminal.write32(reinterpret_cast<char32_t*>(&c), 1);
  1060. } else {
  1061. refresh_line();
  1062. }
  1063. return ( Replxx::ACTION_RESULT::CONTINUE );
  1064. }
  1065. // ctrl-A, HOME: move cursor to start of line
  1066. Replxx::ACTION_RESULT Replxx::ReplxxImpl::go_to_begining_of_line( char32_t ) {
  1067. _pos = 0;
  1068. return ( Replxx::ACTION_RESULT::CONTINUE );
  1069. }
  1070. Replxx::ACTION_RESULT Replxx::ReplxxImpl::go_to_end_of_line( char32_t ) {
  1071. _pos = _data.length();
  1072. return ( Replxx::ACTION_RESULT::CONTINUE );
  1073. }
  1074. // ctrl-B, move cursor left by one character
  1075. Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_char_left( char32_t ) {
  1076. if (_pos > 0) {
  1077. --_pos;
  1078. refresh_line();
  1079. }
  1080. return ( Replxx::ACTION_RESULT::CONTINUE );
  1081. }
  1082. // ctrl-F, move cursor right by one character
  1083. Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_char_right( char32_t ) {
  1084. if ( _pos < _data.length() ) {
  1085. ++_pos;
  1086. refresh_line();
  1087. }
  1088. return ( Replxx::ACTION_RESULT::CONTINUE );
  1089. }
  1090. // meta-B, move cursor left by one word
  1091. Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_left( char32_t ) {
  1092. if (_pos > 0) {
  1093. while (_pos > 0 && is_word_break_character( _data[_pos - 1] ) ) {
  1094. --_pos;
  1095. }
  1096. while (_pos > 0 && !is_word_break_character( _data[_pos - 1] ) ) {
  1097. --_pos;
  1098. }
  1099. refresh_line();
  1100. }
  1101. return ( Replxx::ACTION_RESULT::CONTINUE );
  1102. }
  1103. // meta-F, move cursor right by one word
  1104. Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_right( char32_t ) {
  1105. if ( _pos < _data.length() ) {
  1106. while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) {
  1107. ++_pos;
  1108. }
  1109. while ( _pos < _data.length() && !is_word_break_character( _data[_pos] ) ) {
  1110. ++_pos;
  1111. }
  1112. refresh_line();
  1113. }
  1114. return ( Replxx::ACTION_RESULT::CONTINUE );
  1115. }
  1116. // meta-Backspace, kill word to left of cursor
  1117. Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_left( char32_t ) {
  1118. if ( _pos > 0 ) {
  1119. _history.reset_recall_most_recent();
  1120. int startingPos = _pos;
  1121. while ( _pos > 0 && is_word_break_character( _data[_pos - 1] ) ) {
  1122. -- _pos;
  1123. }
  1124. while ( _pos > 0 && !is_word_break_character( _data[_pos - 1] ) ) {
  1125. -- _pos;
  1126. }
  1127. _killRing.kill( _data.get() + _pos, startingPos - _pos, false);
  1128. _data.erase( _pos, startingPos - _pos );
  1129. refresh_line();
  1130. }
  1131. return ( Replxx::ACTION_RESULT::CONTINUE );
  1132. }
  1133. // meta-D, kill word to right of cursor
  1134. Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_right( char32_t ) {
  1135. if ( _pos < _data.length() ) {
  1136. _history.reset_recall_most_recent();
  1137. int endingPos = _pos;
  1138. while ( endingPos < _data.length() && is_word_break_character( _data[endingPos] ) ) {
  1139. ++ endingPos;
  1140. }
  1141. while ( endingPos < _data.length() && !is_word_break_character( _data[endingPos] ) ) {
  1142. ++ endingPos;
  1143. }
  1144. _killRing.kill( _data.get() + _pos, endingPos - _pos, true );
  1145. _data.erase( _pos, endingPos - _pos );
  1146. refresh_line();
  1147. }
  1148. return ( Replxx::ACTION_RESULT::CONTINUE );
  1149. }
  1150. // ctrl-W, kill to whitespace (not word) to left of cursor
  1151. Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_whitespace_to_left( char32_t ) {
  1152. if ( _pos > 0 ) {
  1153. _history.reset_recall_most_recent();
  1154. int startingPos = _pos;
  1155. while ( _pos > 0 && _data[_pos - 1] == ' ' ) {
  1156. --_pos;
  1157. }
  1158. while ( _pos > 0 && _data[_pos - 1] != ' ' ) {
  1159. -- _pos;
  1160. }
  1161. _killRing.kill( _data.get() + _pos, startingPos - _pos, false );
  1162. _data.erase( _pos, startingPos - _pos );
  1163. refresh_line();
  1164. }
  1165. return ( Replxx::ACTION_RESULT::CONTINUE );
  1166. }
  1167. // ctrl-K, kill from cursor to end of line
  1168. Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_end_of_line( char32_t ) {
  1169. _killRing.kill( _data.get() + _pos, _data.length() - _pos, true );
  1170. _data.erase( _pos, _data.length() - _pos );
  1171. _history.reset_recall_most_recent();
  1172. return ( Replxx::ACTION_RESULT::CONTINUE );
  1173. }
  1174. // ctrl-U, kill all characters to the left of the cursor
  1175. Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_begining_of_line( char32_t ) {
  1176. if (_pos > 0) {
  1177. _history.reset_recall_most_recent();
  1178. _killRing.kill( _data.get(), _pos, false );
  1179. _data.erase( 0, _pos );
  1180. _pos = 0;
  1181. refresh_line();
  1182. }
  1183. return ( Replxx::ACTION_RESULT::CONTINUE );
  1184. }
  1185. // ctrl-Y, yank killed text
  1186. Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank( char32_t ) {
  1187. _history.reset_recall_most_recent();
  1188. UnicodeString* restoredText( _killRing.yank() );
  1189. if ( restoredText ) {
  1190. _data.insert( _pos, *restoredText, 0, restoredText->length() );
  1191. _pos += restoredText->length();
  1192. refresh_line();
  1193. _killRing.lastAction = KillRing::actionYank;
  1194. _killRing.lastYankSize = restoredText->length();
  1195. } else {
  1196. beep();
  1197. }
  1198. return ( Replxx::ACTION_RESULT::CONTINUE );
  1199. }
  1200. // meta-Y, "yank-pop", rotate popped text
  1201. Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank_cycle( char32_t ) {
  1202. if ( _killRing.lastAction != KillRing::actionYank ) {
  1203. beep();
  1204. return ( Replxx::ACTION_RESULT::CONTINUE );
  1205. }
  1206. _history.reset_recall_most_recent();
  1207. UnicodeString* restoredText = _killRing.yankPop();
  1208. if ( !restoredText ) {
  1209. beep();
  1210. return ( Replxx::ACTION_RESULT::CONTINUE );
  1211. }
  1212. _pos -= _killRing.lastYankSize;
  1213. _data.erase( _pos, _killRing.lastYankSize );
  1214. _data.insert( _pos, *restoredText, 0, restoredText->length() );
  1215. _pos += restoredText->length();
  1216. _killRing.lastYankSize = restoredText->length();
  1217. refresh_line();
  1218. return ( Replxx::ACTION_RESULT::CONTINUE );
  1219. }
  1220. // meta-C, give word initial Cap
  1221. Replxx::ACTION_RESULT Replxx::ReplxxImpl::capitalize_word( char32_t ) {
  1222. _history.reset_recall_most_recent();
  1223. if (_pos < _data.length()) {
  1224. while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) {
  1225. ++_pos;
  1226. }
  1227. if (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) {
  1228. if ( _data[_pos] >= 'a' && _data[_pos] <= 'z' ) {
  1229. _data[_pos] += 'A' - 'a';
  1230. }
  1231. ++_pos;
  1232. }
  1233. while (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) {
  1234. if ( _data[_pos] >= 'A' && _data[_pos] <= 'Z' ) {
  1235. _data[_pos] += 'a' - 'A';
  1236. }
  1237. ++_pos;
  1238. }
  1239. refresh_line();
  1240. }
  1241. return ( Replxx::ACTION_RESULT::CONTINUE );
  1242. }
  1243. // meta-L, lowercase word
  1244. Replxx::ACTION_RESULT Replxx::ReplxxImpl::lowercase_word( char32_t ) {
  1245. if (_pos < _data.length()) {
  1246. _history.reset_recall_most_recent();
  1247. while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) {
  1248. ++ _pos;
  1249. }
  1250. while (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) {
  1251. if ( _data[_pos] >= 'A' && _data[_pos] <= 'Z' ) {
  1252. _data[_pos] += 'a' - 'A';
  1253. }
  1254. ++ _pos;
  1255. }
  1256. refresh_line();
  1257. }
  1258. return ( Replxx::ACTION_RESULT::CONTINUE );
  1259. }
  1260. // meta-U, uppercase word
  1261. Replxx::ACTION_RESULT Replxx::ReplxxImpl::uppercase_word( char32_t ) {
  1262. if (_pos < _data.length()) {
  1263. _history.reset_recall_most_recent();
  1264. while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) {
  1265. ++ _pos;
  1266. }
  1267. while ( _pos < _data.length() && !is_word_break_character( _data[_pos] ) ) {
  1268. if ( _data[_pos] >= 'a' && _data[_pos] <= 'z') {
  1269. _data[_pos] += 'A' - 'a';
  1270. }
  1271. ++ _pos;
  1272. }
  1273. refresh_line();
  1274. }
  1275. return ( Replxx::ACTION_RESULT::CONTINUE );
  1276. }
  1277. // ctrl-T, transpose characters
  1278. Replxx::ACTION_RESULT Replxx::ReplxxImpl::transpose_characters( char32_t ) {
  1279. if ( _pos > 0 && _data.length() > 1 ) {
  1280. _history.reset_recall_most_recent();
  1281. size_t leftCharPos = ( _pos == _data.length() ) ? _pos - 2 : _pos - 1;
  1282. char32_t aux = _data[leftCharPos];
  1283. _data[leftCharPos] = _data[leftCharPos + 1];
  1284. _data[leftCharPos + 1] = aux;
  1285. if ( _pos != _data.length() ) {
  1286. ++_pos;
  1287. }
  1288. refresh_line();
  1289. }
  1290. return ( Replxx::ACTION_RESULT::CONTINUE );
  1291. }
  1292. // ctrl-C, abort this line
  1293. Replxx::ACTION_RESULT Replxx::ReplxxImpl::abort_line( char32_t ) {
  1294. _history.reset_recall_most_recent();
  1295. errno = EAGAIN;
  1296. _history.drop_last();
  1297. // we need one last refresh with the cursor at the end of the line
  1298. // so we don't display the next prompt over the previous input line
  1299. _pos = _data.length(); // pass _data.length() as _pos for EOL
  1300. refresh_line( HINT_ACTION::TRIM );
  1301. _terminal.write8( "^C\r\n", 4 );
  1302. return ( Replxx::ACTION_RESULT::BAIL );
  1303. }
  1304. // DEL, delete the character under the cursor
  1305. Replxx::ACTION_RESULT Replxx::ReplxxImpl::delete_character( char32_t ) {
  1306. if ( ( _data.length() > 0 ) && ( _pos < _data.length() ) ) {
  1307. _history.reset_recall_most_recent();
  1308. _data.erase( _pos );
  1309. refresh_line();
  1310. }
  1311. return ( Replxx::ACTION_RESULT::CONTINUE );
  1312. }
  1313. // ctrl-D, delete the character under the cursor
  1314. // on an empty line, exit the shell
  1315. Replxx::ACTION_RESULT Replxx::ReplxxImpl::send_eof( char32_t key_ ) {
  1316. if ( _data.length() == 0 ) {
  1317. _history.drop_last();
  1318. return ( Replxx::ACTION_RESULT::BAIL );
  1319. }
  1320. return ( delete_character( key_ ) );
  1321. }
  1322. // backspace/ctrl-H, delete char to left of cursor
  1323. Replxx::ACTION_RESULT Replxx::ReplxxImpl::backspace_character( char32_t ) {
  1324. if ( _pos > 0 ) {
  1325. _history.reset_recall_most_recent();
  1326. -- _pos;
  1327. _data.erase( _pos );
  1328. refresh_line();
  1329. }
  1330. return ( Replxx::ACTION_RESULT::CONTINUE );
  1331. }
  1332. // ctrl-J/linefeed/newline, accept line
  1333. // ctrl-M/return/enter
  1334. Replxx::ACTION_RESULT Replxx::ReplxxImpl::commit_line( char32_t ) {
  1335. // we need one last refresh with the cursor at the end of the line
  1336. // so we don't display the next prompt over the previous input line
  1337. _pos = _data.length(); // pass _data.length() as _pos for EOL
  1338. refresh_line( HINT_ACTION::TRIM );
  1339. _history.commit_index();
  1340. _history.drop_last();
  1341. return ( Replxx::ACTION_RESULT::RETURN );
  1342. }
  1343. // ctrl-N, recall next line in history
  1344. Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_next( char32_t ) {
  1345. return ( history_move( false ) );
  1346. }
  1347. // ctrl-P, recall previous line in history
  1348. Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_previous( char32_t ) {
  1349. return ( history_move( true ) );
  1350. }
  1351. Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_move( bool previous_ ) {
  1352. // if not already recalling, add the current line to the history list so
  1353. // we don't
  1354. // have to special case it
  1355. if ( _history.is_last() ) {
  1356. _history.update_last( _data );
  1357. }
  1358. if ( _history.is_empty() ) {
  1359. return ( Replxx::ACTION_RESULT::CONTINUE );
  1360. }
  1361. if ( ! _history.move( previous_ ) ) {
  1362. return ( Replxx::ACTION_RESULT::CONTINUE );
  1363. }
  1364. _data.assign( _history.current() );
  1365. _pos = _data.length();
  1366. refresh_line();
  1367. return ( Replxx::ACTION_RESULT::CONTINUE );
  1368. }
  1369. // meta-<, beginning of history
  1370. // Page Up, beginning of history
  1371. Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_first( char32_t ) {
  1372. return ( history_jump( true ) );
  1373. }
  1374. // meta->, end of history
  1375. // Page Down, end of history
  1376. Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_last( char32_t ) {
  1377. return ( history_jump( false ) );
  1378. }
  1379. Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_jump( bool back_ ) {
  1380. // if not already recalling, add the current line to the history list so
  1381. // we don't
  1382. // have to special case it
  1383. if ( _history.is_last() ) {
  1384. _history.update_last( _data );
  1385. }
  1386. if ( ! _history.is_empty() ) {
  1387. _history.jump( back_ );
  1388. _data.assign( _history.current() );
  1389. _pos = _data.length();
  1390. refresh_line();
  1391. }
  1392. return ( Replxx::ACTION_RESULT::CONTINUE );
  1393. }
  1394. Replxx::ACTION_RESULT Replxx::ReplxxImpl::hint_next( char32_t ) {
  1395. return ( hint_move( false ) );
  1396. }
  1397. Replxx::ACTION_RESULT Replxx::ReplxxImpl::hint_previous( char32_t ) {
  1398. return ( hint_move( true ) );
  1399. }
  1400. Replxx::ACTION_RESULT Replxx::ReplxxImpl::hint_move( bool previous_ ) {
  1401. if ( ! _noColor ) {
  1402. _killRing.lastAction = KillRing::actionOther;
  1403. if ( previous_ ) {
  1404. -- _hintSelection;
  1405. } else {
  1406. ++ _hintSelection;
  1407. }
  1408. refresh_line( HINT_ACTION::REPAINT );
  1409. }
  1410. return ( Replxx::ACTION_RESULT::CONTINUE );
  1411. }
  1412. Replxx::ACTION_RESULT Replxx::ReplxxImpl::toggle_overwrite_mode( char32_t ) {
  1413. _overwrite = ! _overwrite;
  1414. return ( Replxx::ACTION_RESULT::CONTINUE );
  1415. }
  1416. #ifndef _WIN32
  1417. Replxx::ACTION_RESULT Replxx::ReplxxImpl::verbatim_insert( char32_t ) {
  1418. static int const MAX_ESC_SEQ( 32 );
  1419. char32_t buf[MAX_ESC_SEQ];
  1420. int len( _terminal.read_verbatim( buf, MAX_ESC_SEQ ) );
  1421. _data.insert( _pos, UnicodeString( buf, len ), 0, len );
  1422. _pos += len;
  1423. return ( Replxx::ACTION_RESULT::CONTINUE );
  1424. }
  1425. // ctrl-Z, job control
  1426. Replxx::ACTION_RESULT Replxx::ReplxxImpl::suspend( char32_t ) {
  1427. _terminal.disable_raw_mode(); // Returning to Linux (whatever) shell, leave raw mode
  1428. raise(SIGSTOP); // Break out in mid-line
  1429. _terminal.enable_raw_mode(); // Back from Linux shell, re-enter raw mode
  1430. // Redraw prompt
  1431. _prompt.write();
  1432. return ( Replxx::ACTION_RESULT::CONTINUE );
  1433. }
  1434. #endif
  1435. Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_line( char32_t c ) {
  1436. if ( !! _completionCallback && ( _completeOnEmpty || ( _pos > 0 ) ) ) {
  1437. _killRing.lastAction = KillRing::actionOther;
  1438. _history.reset_recall_most_recent();
  1439. // complete_line does the actual completion and replacement
  1440. c = do_complete_line( c != 0 );
  1441. if ( static_cast<int>( c ) < 0 ) {
  1442. return ( Replxx::ACTION_RESULT::BAIL );
  1443. }
  1444. if ( c != 0 ) {
  1445. emulate_key_press( c );
  1446. }
  1447. } else {
  1448. insert_character( c );
  1449. }
  1450. return ( Replxx::ACTION_RESULT::CONTINUE );
  1451. }
  1452. Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete( bool previous_ ) {
  1453. if ( _completions.empty() ) {
  1454. bool first( _completions.empty() );
  1455. complete_line( first ? '\t' : 0 );
  1456. if ( first ) {
  1457. return ( Replxx::ACTION_RESULT::CONTINUE );
  1458. }
  1459. }
  1460. int newSelection( _completionSelection + ( previous_ ? -1 : 1 ) );
  1461. if ( newSelection >= static_cast<int>( _completions.size() ) ) {
  1462. newSelection = -1;
  1463. } else if ( newSelection == -2 ) {
  1464. newSelection = static_cast<int>( _completions.size() ) - 1;
  1465. }
  1466. if ( _completionSelection != -1 ) {
  1467. int oldCompletionLength( _completions[_completionSelection].text().length() - _completionContextLength );
  1468. _pos -= oldCompletionLength;
  1469. _data.erase( _pos, oldCompletionLength );
  1470. }
  1471. if ( newSelection != -1 ) {
  1472. int newCompletionLength( _completions[newSelection].text().length() - _completionContextLength );
  1473. _data.insert( _pos, _completions[newSelection].text(), _completionContextLength, newCompletionLength );
  1474. _pos += newCompletionLength;
  1475. }
  1476. _completionSelection = newSelection;
  1477. refresh_line(); // Refresh the line
  1478. return ( Replxx::ACTION_RESULT::CONTINUE );
  1479. }
  1480. Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_next( char32_t ) {
  1481. return ( complete( false ) );
  1482. }
  1483. Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_previous( char32_t ) {
  1484. return ( complete( true ) );
  1485. }
  1486. // Alt-P, reverse history search for prefix
  1487. // Alt-P, reverse history search for prefix
  1488. // Alt-N, forward history search for prefix
  1489. // Alt-N, forward history search for prefix
  1490. Replxx::ACTION_RESULT Replxx::ReplxxImpl::common_prefix_search( char32_t startChar ) {
  1491. int prefixSize( calculate_displayed_length( _data.get(), _prefix ) );
  1492. if (
  1493. _history.common_prefix_search(
  1494. _data, prefixSize, ( startChar == ( Replxx::KEY::meta( 'p' ) ) ) || ( startChar == ( Replxx::KEY::meta( 'P' ) ) )
  1495. )
  1496. ) {
  1497. _data.assign( _history.current() );
  1498. _pos = _data.length();
  1499. refresh_line();
  1500. }
  1501. return ( Replxx::ACTION_RESULT::CONTINUE );
  1502. }
  1503. // ctrl-R, reverse history search
  1504. // ctrl-S, forward history search
  1505. /**
  1506. * Incremental history search -- take over the prompt and keyboard as the user
  1507. * types a search string, deletes characters from it, changes _direction,
  1508. * and either accepts the found line (for execution orediting) or cancels.
  1509. * @param startChar - the character that began the search, used to set the initial
  1510. * _direction
  1511. */
  1512. Replxx::ACTION_RESULT Replxx::ReplxxImpl::incremental_history_search( char32_t startChar ) {
  1513. // if not already recalling, add the current line to the history list so we
  1514. // don't have to special case it
  1515. if ( _history.is_last() ) {
  1516. _history.update_last( _data );
  1517. }
  1518. int historyLinePosition( _pos );
  1519. clear_self_to_end_of_screen();
  1520. DynamicPrompt dp( _terminal, (startChar == Replxx::KEY::control('R')) ? -1 : 1 );
  1521. dp._previousLen = _prompt._previousLen;
  1522. dp._previousInputLen = _prompt._previousInputLen;
  1523. // draw user's text with our prompt
  1524. dynamicRefresh(dp, _data.get(), _data.length(), historyLinePosition);
  1525. // loop until we get an exit character
  1526. char32_t c( 0 );
  1527. bool keepLooping = true;
  1528. bool useSearchedLine = true;
  1529. bool searchAgain = false;
  1530. UnicodeString activeHistoryLine;
  1531. while ( keepLooping ) {
  1532. c = read_char();
  1533. switch (c) {
  1534. // these characters keep the selected text but do not execute it
  1535. case Replxx::KEY::control('A'): // ctrl-A, move cursor to start of line
  1536. case Replxx::KEY::HOME:
  1537. case Replxx::KEY::control('B'): // ctrl-B, move cursor left by one character
  1538. case Replxx::KEY::LEFT:
  1539. case Replxx::KEY::meta( 'b' ): // meta-B, move cursor left by one word
  1540. case Replxx::KEY::meta( 'B' ):
  1541. case Replxx::KEY::control( Replxx::KEY::LEFT ):
  1542. case Replxx::KEY::meta( Replxx::KEY::LEFT ): // Emacs allows Meta, bash & readline don't
  1543. case Replxx::KEY::control('D'):
  1544. case Replxx::KEY::meta( 'd' ): // meta-D, kill word to right of cursor
  1545. case Replxx::KEY::meta( 'D' ):
  1546. case Replxx::KEY::control('E'): // ctrl-E, move cursor to end of line
  1547. case Replxx::KEY::END:
  1548. case Replxx::KEY::control('F'): // ctrl-F, move cursor right by one character
  1549. case Replxx::KEY::RIGHT:
  1550. case Replxx::KEY::meta( 'f' ): // meta-F, move cursor right by one word
  1551. case Replxx::KEY::meta( 'F' ):
  1552. case Replxx::KEY::control( Replxx::KEY::RIGHT ):
  1553. case Replxx::KEY::meta( Replxx::KEY::RIGHT ): // Emacs allows Meta, bash & readline don't
  1554. case Replxx::KEY::meta( Replxx::KEY::BACKSPACE ):
  1555. case Replxx::KEY::control('J'):
  1556. case Replxx::KEY::control('K'): // ctrl-K, kill from cursor to end of line
  1557. case Replxx::KEY::ENTER:
  1558. case Replxx::KEY::control('N'): // ctrl-N, recall next line in history
  1559. case Replxx::KEY::control('P'): // ctrl-P, recall previous line in history
  1560. case Replxx::KEY::DOWN:
  1561. case Replxx::KEY::UP:
  1562. case Replxx::KEY::control('T'): // ctrl-T, transpose characters
  1563. case Replxx::KEY::control('U'): // ctrl-U, kill all characters to the left of the cursor
  1564. case Replxx::KEY::control('W'):
  1565. case Replxx::KEY::meta( 'y' ): // meta-Y, "yank-pop", rotate popped text
  1566. case Replxx::KEY::meta( 'Y' ):
  1567. case 127:
  1568. case Replxx::KEY::DELETE:
  1569. case Replxx::KEY::meta( '<' ): // start of history
  1570. case Replxx::KEY::PAGE_UP:
  1571. case Replxx::KEY::meta( '>' ): // end of history
  1572. case Replxx::KEY::PAGE_DOWN:
  1573. keepLooping = false;
  1574. break;
  1575. // these characters revert the input line to its previous state
  1576. case Replxx::KEY::control('C'): // ctrl-C, abort this line
  1577. case Replxx::KEY::control('G'):
  1578. case Replxx::KEY::control('L'): // ctrl-L, clear screen and redisplay line
  1579. keepLooping = false;
  1580. useSearchedLine = false;
  1581. if (c != Replxx::KEY::control('L')) {
  1582. c = -1; // ctrl-C and ctrl-G just abort the search and do nothing else
  1583. }
  1584. break;
  1585. // these characters stay in search mode and assign the display
  1586. case Replxx::KEY::control('S'):
  1587. case Replxx::KEY::control('R'):
  1588. if ( dp._searchText.length() == 0 ) { // if no current search text, recall previous text
  1589. if ( previousSearchText.length() > 0 ) {
  1590. dp._searchText = previousSearchText;
  1591. }
  1592. }
  1593. if ((dp._direction == 1 && c == Replxx::KEY::control('R')) ||
  1594. (dp._direction == -1 && c == Replxx::KEY::control('S'))) {
  1595. dp._direction = 0 - dp._direction; // reverse _direction
  1596. dp.updateSearchPrompt(); // change the prompt
  1597. } else {
  1598. searchAgain = true; // same _direction, search again
  1599. }
  1600. break;
  1601. // job control is its own thing
  1602. #ifndef _WIN32
  1603. case Replxx::KEY::control('Z'): { // ctrl-Z, job control
  1604. _terminal.disable_raw_mode(); // Returning to Linux (whatever) shell, leave raw mode
  1605. raise(SIGSTOP); // Break out in mid-line
  1606. _terminal.enable_raw_mode(); // Back from Linux shell, re-enter raw mode
  1607. dynamicRefresh(dp, activeHistoryLine.get(), activeHistoryLine.length(), historyLinePosition);
  1608. continue;
  1609. } break;
  1610. #endif
  1611. // these characters assign the search string, and hence the selected input
  1612. // line
  1613. case Replxx::KEY::BACKSPACE: // backspace/ctrl-H, delete char to left of cursor
  1614. if ( dp._searchText.length() > 0 ) {
  1615. dp._searchText.erase( dp._searchText.length() - 1 );
  1616. dp.updateSearchPrompt();
  1617. _history.reset_pos( dp._direction == -1 ? _history.size() - 1 : 0 );
  1618. } else {
  1619. beep();
  1620. }
  1621. break;
  1622. case Replxx::KEY::control('Y'): // ctrl-Y, yank killed text
  1623. break;
  1624. default: {
  1625. if ( ! is_control_code( c ) && ( c < static_cast<int>( Replxx::KEY::BASE ) ) ) { // not an action character
  1626. dp._searchText.insert( dp._searchText.length(), c );
  1627. dp.updateSearchPrompt();
  1628. } else {
  1629. beep();
  1630. }
  1631. }
  1632. } // switch
  1633. // if we are staying in search mode, search now
  1634. if ( ! keepLooping ) {
  1635. break;
  1636. }
  1637. activeHistoryLine.assign( _history.current() );
  1638. if ( dp._searchText.length() > 0 ) {
  1639. bool found = false;
  1640. int historySearchIndex = _history.current_pos();
  1641. int lineSearchPos = historyLinePosition;
  1642. if ( searchAgain ) {
  1643. lineSearchPos += dp._direction;
  1644. }
  1645. searchAgain = false;
  1646. while ( true ) {
  1647. while ( ( ( lineSearchPos + dp._searchText.length() ) <= activeHistoryLine.length() ) && ( lineSearchPos >= 0 ) ) {
  1648. if ( std::equal( dp._searchText.begin(), dp._searchText.end(), activeHistoryLine.begin() + lineSearchPos ) ) {
  1649. found = true;
  1650. break;
  1651. }
  1652. lineSearchPos += dp._direction;
  1653. }
  1654. if ( found ) {
  1655. _history.reset_pos( historySearchIndex );
  1656. historyLinePosition = lineSearchPos;
  1657. break;
  1658. } else if ( ( dp._direction > 0 ) ? ( historySearchIndex < _history.size() ) : ( historySearchIndex > 0 ) ) {
  1659. historySearchIndex += dp._direction;
  1660. activeHistoryLine.assign( _history[historySearchIndex] );
  1661. lineSearchPos = ( dp._direction > 0 ) ? 0 : ( activeHistoryLine.length() - dp._searchText.length() );
  1662. } else {
  1663. beep();
  1664. break;
  1665. }
  1666. } // while
  1667. }
  1668. activeHistoryLine.assign( _history.current() );
  1669. dynamicRefresh(dp, activeHistoryLine.get(), activeHistoryLine.length(), historyLinePosition); // draw user's text with our prompt
  1670. } // while
  1671. // leaving history search, restore previous prompt, maybe make searched line
  1672. // current
  1673. Prompt pb( _terminal );
  1674. pb._characterCount = _prompt._indentation;
  1675. pb._byteCount = _prompt._byteCount;
  1676. UnicodeString tempUnicode( &_prompt._text[_prompt._lastLinePosition], pb._byteCount - _prompt._lastLinePosition );
  1677. pb._text = tempUnicode;
  1678. pb._extraLines = 0;
  1679. pb._indentation = _prompt._indentation;
  1680. pb._lastLinePosition = 0;
  1681. pb._previousInputLen = activeHistoryLine.length();
  1682. pb._cursorRowOffset = dp._cursorRowOffset;
  1683. pb.update_screen_columns();
  1684. pb._previousLen = dp._characterCount;
  1685. if ( useSearchedLine && ( activeHistoryLine.length() > 0 ) ) {
  1686. _history.set_recall_most_recent();
  1687. _data.assign( activeHistoryLine );
  1688. _pos = historyLinePosition;
  1689. }
  1690. dynamicRefresh(pb, _data.get(), _data.length(), _pos); // redraw the original prompt with current input
  1691. _prompt._previousInputLen = _data.length();
  1692. _prompt._cursorRowOffset = _prompt._extraLines + pb._cursorRowOffset;
  1693. previousSearchText = dp._searchText; // save search text for possible reuse on ctrl-R ctrl-R
  1694. emulate_key_press( c ); // pass a character or -1 back to main loop
  1695. return ( Replxx::ACTION_RESULT::CONTINUE );
  1696. }
  1697. // ctrl-L, clear screen and redisplay line
  1698. Replxx::ACTION_RESULT Replxx::ReplxxImpl::clear_screen( char32_t c ) {
  1699. _terminal.clear_screen( Terminal::CLEAR_SCREEN::WHOLE );
  1700. if ( c ) {
  1701. _prompt.write();
  1702. #ifndef _WIN32
  1703. // we have to generate our own newline on line wrap on Linux
  1704. if (_prompt._indentation == 0 && _prompt._extraLines > 0) {
  1705. _terminal.write8( "\n", 1 );
  1706. }
  1707. #endif
  1708. _prompt._cursorRowOffset = _prompt._extraLines;
  1709. refresh_line();
  1710. }
  1711. return ( Replxx::ACTION_RESULT::CONTINUE );
  1712. }
  1713. bool Replxx::ReplxxImpl::is_word_break_character( char32_t char_ ) const {
  1714. bool wbc( false );
  1715. if ( char_ < 128 ) {
  1716. wbc = strchr( _breakChars, static_cast<char>( char_ ) ) != nullptr;
  1717. }
  1718. return ( wbc );
  1719. }
  1720. void Replxx::ReplxxImpl::history_add( std::string const& line ) {
  1721. _history.add( UnicodeString( line ) );
  1722. }
  1723. int Replxx::ReplxxImpl::history_save( std::string const& filename ) {
  1724. return ( _history.save( filename ) );
  1725. }
  1726. int Replxx::ReplxxImpl::history_load( std::string const& filename ) {
  1727. return ( _history.load( filename ) );
  1728. }
  1729. int Replxx::ReplxxImpl::history_size( void ) const {
  1730. return ( _history.size() );
  1731. }
  1732. std::string Replxx::ReplxxImpl::history_line( int index ) {
  1733. _utf8Buffer.assign( _history[index] );
  1734. return ( _utf8Buffer.get() );
  1735. }
  1736. void Replxx::ReplxxImpl::set_completion_callback( Replxx::completion_callback_t const& fn ) {
  1737. _completionCallback = fn;
  1738. }
  1739. void Replxx::ReplxxImpl::set_highlighter_callback( Replxx::highlighter_callback_t const& fn ) {
  1740. _highlighterCallback = fn;
  1741. }
  1742. void Replxx::ReplxxImpl::set_hint_callback( Replxx::hint_callback_t const& fn ) {
  1743. _hintCallback = fn;
  1744. }
  1745. void Replxx::ReplxxImpl::set_max_history_size( int len ) {
  1746. _history.set_max_size( len );
  1747. }
  1748. void Replxx::ReplxxImpl::set_completion_count_cutoff( int count ) {
  1749. _completionCountCutoff = count;
  1750. }
  1751. void Replxx::ReplxxImpl::set_max_hint_rows( int count ) {
  1752. _maxHintRows = count;
  1753. }
  1754. void Replxx::ReplxxImpl::set_hint_delay( int hintDelay_ ) {
  1755. _hintDelay = hintDelay_;
  1756. }
  1757. void Replxx::ReplxxImpl::set_word_break_characters( char const* wordBreakers ) {
  1758. _breakChars = wordBreakers;
  1759. }
  1760. void Replxx::ReplxxImpl::set_double_tab_completion( bool val ) {
  1761. _doubleTabCompletion = val;
  1762. }
  1763. void Replxx::ReplxxImpl::set_complete_on_empty( bool val ) {
  1764. _completeOnEmpty = val;
  1765. }
  1766. void Replxx::ReplxxImpl::set_beep_on_ambiguous_completion( bool val ) {
  1767. _beepOnAmbiguousCompletion = val;
  1768. }
  1769. void Replxx::ReplxxImpl::set_no_color( bool val ) {
  1770. _noColor = val;
  1771. }
  1772. /**
  1773. * Display the dynamic incremental search prompt and the current user input
  1774. * line.
  1775. * @param pi Prompt struct holding information about the prompt and our
  1776. * screen position
  1777. * @param buf32 input buffer to be displayed
  1778. * @param len count of characters in the buffer
  1779. * @param pos current cursor position within the buffer (0 <= pos <= len)
  1780. */
  1781. void Replxx::ReplxxImpl::dynamicRefresh(Prompt& pi, char32_t* buf32, int len, int pos) {
  1782. clear_self_to_end_of_screen();
  1783. // calculate the position of the end of the prompt
  1784. int xEndOfPrompt, yEndOfPrompt;
  1785. calculate_screen_position(
  1786. 0, 0, pi.screen_columns(), pi._characterCount,
  1787. xEndOfPrompt, yEndOfPrompt
  1788. );
  1789. pi._indentation = xEndOfPrompt;
  1790. // calculate the position of the end of the input line
  1791. int xEndOfInput, yEndOfInput;
  1792. calculate_screen_position(
  1793. xEndOfPrompt, yEndOfPrompt, pi.screen_columns(),
  1794. calculate_displayed_length(buf32, len), xEndOfInput,
  1795. yEndOfInput
  1796. );
  1797. // calculate the desired position of the cursor
  1798. int xCursorPos, yCursorPos;
  1799. calculate_screen_position(
  1800. xEndOfPrompt, yEndOfPrompt, pi.screen_columns(),
  1801. calculate_displayed_length(buf32, pos), xCursorPos,
  1802. yCursorPos
  1803. );
  1804. pi._previousLen = pi._indentation;
  1805. pi._previousInputLen = len;
  1806. // display the prompt
  1807. pi.write();
  1808. // display the input line
  1809. _terminal.write32( buf32, len );
  1810. #ifndef _WIN32
  1811. // we have to generate our own newline on line wrap
  1812. if (xEndOfInput == 0 && yEndOfInput > 0) {
  1813. _terminal.write8( "\n", 1 );
  1814. }
  1815. #endif
  1816. // position the cursor
  1817. _terminal.jump_cursor(
  1818. xCursorPos, // 0-based on Win32
  1819. -( yEndOfInput - yCursorPos )
  1820. );
  1821. pi._cursorRowOffset = pi._extraLines + yCursorPos; // remember row for next pass
  1822. }
  1823. }