1 //  MIT License
2 //
3 //  Copyright (c) 2018 Chun-Xun Lin, Tsung-Wei Huang and Martin D. F. Wong
4 //
5 //  Permission is hereby granted, free of charge, to any person obtaining a copy
6 //  of this software and associated documentation files (the "Software"), to deal
7 //  in the Software without restriction, including without limitation the rights
8 //  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 //  copies of the Software, and to permit persons to whom the Software is
10 //  furnished to do so, subject to the following conditions:
11 //
12 //  The above copyright notice and this permission notice shall be included in all
13 //  copies or substantial portions of the Software.
14 //
15 //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 //  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 //  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 //  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 //  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 //  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 //  SOFTWARE.
22 
23 #ifndef PROMPT_HPP_
24 #define PROMPT_HPP_
25 
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <sys/ioctl.h>
29 #include <termios.h>
30 #include <pwd.h>
31 #include <errno.h>
32 #include <unistd.h>
33 #include <algorithm>
34 #include <unistd.h>
35 #include <sstream>
36 #include <iostream>
37 #include <memory>
38 #include <list>
39 #include <vector>
40 #include <cstring>
41 #include <fstream>
42 #include <string_view>
43 #include <filesystem>
44 #include <cassert>
45 
46 // ------------------------------------------------------------------------------------------------
47 
48 namespace prompt {
49 
50 // Procedure: read_line
51 // Read one line from an input stream
52 //template <typename C, typename T, typename A>
53 //std::basic_istream<C, T>& read_line(
54 //  std::basic_istream<C, T>& is,
55 //  std::basic_string<C, T, A>& line
56 //) {
57 //
58 //  line.clear();
59 //
60 //  typename std::basic_istream<C, T>::sentry se(is, true);
61 //  std::streambuf* sb = is.rdbuf();
62 //
63 //  for(;;) {
64 //    switch (int c = sb->sbumpc(); c) {
65 //
66 //      // case 1: newline
67 //      case '\n':
68 //        return is;
69 //      break;
70 //
71 //      // case 2: carriage return
72 //      case '\r':
73 //        if(sb->sgetc() == '\n'){
74 //          sb->sbumpc();
75 //        }
76 //        return is;
77 //      break;
78 //
79 //      // case 3: eof
80 //      case std::streambuf::traits_type::eof():
81 //        // Also handle the case when the last line has no line ending
82 //        if(line.empty()) {
83 //          is.setstate(std::ios::eofbit | std::ios::failbit);
84 //        }
85 //        return is;
86 //      break;
87 //
88 //      default:
89 //        line.push_back(static_cast<char>(c));
90 //      break;
91 //    }
92 //  }
93 //
94 //  return is;
95 //}
96 
97 
98 
99 template <typename C, typename T, typename A>
read_line(FILE * is,std::basic_string<C,T,A> & line)100 bool read_line(
101   FILE* is,
102   std::basic_string<C, T, A>& line
103 ) {
104 
105   line.clear();
106 
107   for(;;) {
108     switch (int c = ::fgetc(is); c) {
109 
110       // case 1: newline
111       case '\n':
112         return true;
113       break;
114 
115       // case 2: carriage return '\r\n'
116       case '\r':
117         if(auto nc = ::fgetc(is); nc != '\n'){
118           ::ungetc(nc, is);
119         }
120         return true;
121       break;
122 
123       // case 3: eof
124       case EOF:
125         // Also handle the case when the last line has no line ending
126         if(line.empty()) {
127           // The line is empty (only EOF)
128           return false;
129         }
130         return true;
131       break;
132 
133       default:
134         line.push_back(static_cast<char>(c));
135       break;
136     }
137   }
138   return true;
139 }
140 
141 
142 // ------------------------------------------------------------------------------------------------
143 
144 // Function: count_prefix
145 // Count the the length of same prefix between two strings
146 template <typename C>
count_prefix(std::basic_string_view<typename C::value_type,typename C::traits_type> s1,std::basic_string_view<typename C::value_type,typename C::traits_type> s2)147 constexpr size_t count_prefix(
148   std::basic_string_view<typename C::value_type, typename C::traits_type> s1,
149   std::basic_string_view<typename C::value_type, typename C::traits_type> s2){
150   size_t i {0};
151   size_t len {std::min(s1.size(), s2.size())};
152   for(; i<len; ++i){
153     if(s1[i] != s2[i]){
154       break;
155     }
156   }
157   return i;
158 }
159 
160 // ------------------------------------------------------------------------------------------------
161 
162 // Class: RadixTree
163 template <typename C>
164 class RadixTree{
165 
166   using value_type     = typename C::value_type;
167   using traits_type    = typename C::traits_type;
168   using allocator_type = typename C::allocator_type;
169 
170   public:
171 
172     struct Node {
173       bool is_word {false};
174       std::list<std::pair<C, std::unique_ptr<Node>>> children;
175     };
176 
177    RadixTree() = default;
178    RadixTree(const std::vector<C>&);
179 
180    bool exist(std::basic_string_view<value_type, traits_type>) const;
181    void insert(const C&);
182 
183    std::vector<C> match_prefix(const C&) const;
184    std::vector<C> all_words() const;
185    C dump() const;
186 
187    const Node& root() const;
188 
189   private:
190 
191    Node _root;
192 
193    void _insert(std::basic_string_view<value_type, traits_type>, Node&);
194    void _match_prefix(std::vector<C>&, const Node&, const C&) const;
195    void _dump(const Node&, size_t, C&) const;
196 
197    std::pair<const Node*, C> _search_prefix_node(
198      std::basic_string_view<value_type, traits_type>
199    ) const;
200 };
201 
202 
203 // Procedure: Ctor
204 template <typename C>
RadixTree(const std::vector<C> & words)205 RadixTree<C>::RadixTree(const std::vector<C>& words){
206   for(const auto& w: words){
207     insert(w);
208   }
209 }
210 
211 
212 // Procedure: dump
213 // Dump the radix tree into a string
214 template <typename C>
dump() const215 C RadixTree<C>::dump() const {
216   C t;
217   _dump(_root, 0, t);
218   t.append(1, '\n');
219   return t;
220 }
221 
222 // Procedure: _dump
223 // Recursively traverse the tree and append each level to string
224 template <typename C>
_dump(const Node & n,size_t level,C & s) const225 void RadixTree<C>::_dump(const Node& n, size_t level, C& s) const {
226   for(auto &[k,v]: n.children){
227     s.append(level, '-');
228     s.append(1, ' ');
229     s += k;
230     s.append(1, '\n');
231     _dump(*v, level+1, s);
232   }
233 }
234 
235 // Procedure: _match_prefix
236 // Find all words that match the given prefix
237 template <typename C>
_match_prefix(std::vector<C> & vec,const Node & n,const C & s) const238 void RadixTree<C>::_match_prefix(
239   std::vector<C> &vec,
240   const Node& n,
241   const C& s
242 ) const {
243   if(n.is_word){
244     vec.emplace_back(s);
245   }
246   for(auto& [k,v]:n.children){
247     _match_prefix(vec, *v, s+k);
248   }
249 }
250 
251 // Procedure: all_words
252 // Extract all words in the radix tree
253 template <typename C>
all_words() const254 std::vector<C> RadixTree<C>::all_words() const {
255   std::vector<C> words;
256   for(auto &[k, v]: _root.children){
257     _match_prefix(words, *v, k);
258   }
259   return words;
260 }
261 
262 // Procedure: match_prefix
263 // Collect all words that match the given prefix
264 template <typename C>
match_prefix(const C & prefix) const265 std::vector<C> RadixTree<C>::match_prefix(const C& prefix) const {
266   if(auto [prefix_node, suffix] = _search_prefix_node(prefix); prefix_node == nullptr){
267     return {};
268   }
269   else{
270     std::vector<C> matches;
271     if(prefix_node->is_word){
272       matches.emplace_back(prefix + suffix);
273     }
274     for(auto &[k, v]: prefix_node->children){
275       _match_prefix(matches, *v, prefix+suffix+k);
276     }
277     return matches;
278   }
279 }
280 
281 // Procedure: _search_prefix_node
282 // Find the node that matches the given prefix
283 template <typename C>
_search_prefix_node(std::basic_string_view<value_type,traits_type> s) const284 std::pair<const typename RadixTree<C>::Node*, C> RadixTree<C>::_search_prefix_node(
285   std::basic_string_view<value_type, traits_type> s
286 ) const {
287 
288   Node const *n = &_root;
289   std::basic_string<value_type, traits_type> suffix;
290   for(size_t pos=0; pos<s.size(); ){ // Search until full match
291     bool match = {false};
292     for(const auto& [k, v]: n->children){
293       if(auto num = count_prefix<C>(k, s.data()+pos); num>0){
294         pos += num;
295         if(pos == s.size()){
296           suffix = k.substr(num, k.size()-num);
297         }
298         n = v.get();
299         match = true;
300         break;
301       }
302     }
303     if(not match){
304       return {nullptr, suffix};
305     }
306   }
307   return {n, suffix};
308 }
309 
310 // Procedure: exist
311 // Check whether the given word is in the radix tree or not
312 template <typename C>
exist(std::basic_string_view<value_type,traits_type> s) const313 bool RadixTree<C>::exist(std::basic_string_view<value_type, traits_type> s) const {
314 
315   size_t pos {0};
316   Node const *n = &_root;
317   while(not n->children.empty()){ // Search until reaching the leaf
318     bool match {false};
319     for(const auto& [k, v]: n->children){
320       auto match_num = count_prefix<C>(k, s.data()+pos);
321       if(match_num > 0){
322         if(pos += match_num; pos == s.size()){
323           if(match_num == k.size() and v->is_word){
324             return true;
325           }
326         }
327         else{
328           n = v.get();
329           match = true;
330         }
331         break;
332       }
333     }
334     if(not match){
335       return false;
336     }
337   }
338   return false;
339 }
340 
341 // Procedure: insert
342 // Insert a word into radix tree
343 template <typename C>
insert(const C & s)344 void RadixTree<C>::insert(const C& s){
345   if(s.empty()){  // Empty string not allowed
346     return;
347   }
348   _insert(s, _root);
349 }
350 
351 // Procedure: _insert
352 // Insert a word into radix tree
353 template <typename C>
_insert(std::basic_string_view<value_type,traits_type> sv,Node & n)354 void RadixTree<C>::_insert(std::basic_string_view<value_type, traits_type> sv, Node& n){
355 
356   // base case
357   if(sv.empty()) {
358     n.is_word = true;
359     return;
360   }
361 
362   size_t match_num {0};
363 
364   auto itr = std::find_if(n.children.begin(), n.children.end(), [&] (const auto& kv) {
365     if(match_num = count_prefix<C>(std::get<0>(kv), sv); match_num > 0) {
366       return true;
367     }
368     else return false;
369   });
370 
371   if(match_num == 0){   // Base case 1
372     assert(itr == n.children.end());
373     auto& child = std::get<1>(n.children.emplace_back(std::make_pair(sv, new Node)));
374     child->is_word = true;
375   }
376   else {
377 
378     if(match_num == itr->first.size()) {
379       _insert(sv.data() + match_num, *itr->second);
380     }
381     else {
382 
383       auto& par = std::get<1>(n.children.emplace_back(
384         std::make_pair(
385           std::basic_string_view<value_type, traits_type>{sv.data(), match_num}, new Node))
386       );
387 
388       par->children.emplace_back(
389         std::make_pair(itr->first.data()+match_num, std::move(itr->second))
390       );
391 
392       n.children.erase(itr);
393 
394       _insert(sv.data() + match_num, *par);
395     }
396   }
397 }
398 
399 
400 // Procedure: root
401 // Return the root of RadixTree
402 template <typename C>
root() const403 const typename RadixTree<C>::Node& RadixTree<C>::root() const{
404   return _root;
405 }
406 
407 // ------------------------------------------------------------------------------------------------
408 
409 
410 // http://www.physics.udel.edu/~watson/scen103/ascii.html
411 enum class KEY{
412   KEY_NULL = 0,       /* NULL      */
413   CTRL_A   = 1,       /* Ctrl+a    */
414   CTRL_B   = 2,       /* Ctrl-b    */
415   CTRL_C   = 3,       /* Ctrl-c    */
416   CTRL_D   = 4,       /* Ctrl-d    */
417   CTRL_E   = 5,       /* Ctrl-e    */
418   CTRL_F   = 6,       /* Ctrl-f    */
419   CTRL_H   = 8,       /* Ctrl-h    */
420   TAB      = 9,       /* Tab       */
421   CTRL_K   = 11,      /* Ctrl+k    */
422   CTRL_L   = 12,      /* Ctrl+l    */
423   ENTER    = 13,      /* Enter     */
424   CTRL_N   = 14,      /* Ctrl-n    */
425   CTRL_P   = 16,      /* Ctrl-p    */
426   CTRL_T   = 20,      /* Ctrl-t    */
427   CTRL_U   = 21,      /* Ctrl+u    */
428   CTRL_W   = 23,      /* Ctrl+w    */
429   ESC      = 27,      /* Escape    */
430   BACKSPACE =  127    /* Backspace */
431 };
432 
433 enum class COLOR{
434   BLACK = 30,
435   RED,
436   GREEN,
437   YELLOW,
438   BLUE,
439   MAGNETA,
440   CYAN,
441   WHITE
442 };
443 
444 class Prompt {
445 
446   struct LineInfo{
447 
448     std::string buf;
449     int history_trace {0};
450     size_t cur_pos {0};   // cursor position
451 
452     void operator = (const LineInfo&);
resetprompt::Prompt::LineInfo453     inline void reset(){
454       cur_pos = history_trace = 0;
455       buf.clear();
456     }
457   };
458 
459   public:
460 
461     //Prompt(
462     //  const std::string&,   // Welcome message
463     //  const std::string&,   // prompt
464     //  const filesystem::path& = filesystem::current_path()/".prompt_history",  // history log path
465     //  std::istream& = std::cin,
466     //  std::ostream& = std::cout,
467     //  std::ostream& = std::cerr,
468     //  int = STDIN_FILENO
469     //);
470 
471     //// TODO:
472     Prompt(
473       const std::string&,   // Welcome message
474       const std::string&,   // prompt
475       const std::filesystem::path& = std::filesystem::current_path()/".prompt_history",  // history log path
476       FILE* = stdin,
477       std::ostream& = std::cout,
478       std::ostream& = std::cerr
479     );
480 
481     ~Prompt();
482 
483     bool readline(std::string&);
484 
485     void set_history_size(size_t);
history_size() const486     size_t history_size() const { return _history.size(); };
487 
488     void autocomplete(const std::string&);
489 
490   private:
491 
492     std::string _prompt;
493     std::filesystem::path _history_path;
494 
495     // TODO: replace these with FILE* _is, _os, _es;
496     // fix inserters and extractors
497     //std::istream& _cin;
498     FILE* _is;
499     std::ostream& _cout;
500     std::ostream& _cerr;
501 
502 
503     // TODO: remove _infd and use ::fileno(_is);
504     //int _infd;
505 
506     size_t _columns {80};   // default width of terminal is 80
507 
508     RadixTree<std::string> _tree;  // Radix tree for command autocomplete
509 
510     std::string _obuf;      // Buffer for _refresh_single_line
511 
512     bool _unsupported_term();
513     bool _stdin_not_tty(std::string &);
514     bool _set_raw_mode();
515     void _disable_raw_mode();
516 
517     // History
518     size_t _max_history_size {100};
519     std::list<std::string> _history;
520     void _add_history(const std::string&);
521     void _save_history();
522     void _load_history();
523 
524     termios _orig_termios;
525     bool _has_orig_termios {false};
526     bool _save_orig_termios(int);
527 
528     int _get_cursor_pos();
529     void _clear_screen();
530     size_t _terminal_columns();
531 
532     void _edit_line(std::string&);
533 
534     void _refresh_single_line(LineInfo&);
535 
536     LineInfo _line;
537     LineInfo _line_save;
538 
539     int _autocomplete_iterate_command();
540     void _autocomplete_command();
541     void _autocomplete_folder();
542 
543     std::vector<std::string> _files_in_folder(const std::filesystem::path&) const;
544     std::vector<std::string> _files_match_prefix(const std::filesystem::path&) const;
545     std::string _dump_files(const std::vector<std::string>&, const std::filesystem::path&);
546     std::string _dump_options(const std::vector<std::string>&);
547     std::string _next_prefix(const std::vector<std::string>&, const size_t);
548 
549 
550     std::filesystem::path _user_home() const;
551     bool _has_read_access(const std::filesystem::path&) const;
552 
553     // Key handling subroutine
554     void _key_backspace(LineInfo&);
555     void _key_delete(LineInfo&);
556     void _key_delete_prev_word(LineInfo&);
557 
558     void _key_prev_history(LineInfo&);
559     void _key_next_history(LineInfo&);
560     void _key_history(LineInfo&, bool);
561     bool _key_handle_CSI(LineInfo&);
562 
563     bool _append_character(LineInfo&, char);
564 };
565 
566 
567 // Copy assignment
operator =(const LineInfo & l)568 inline void Prompt::LineInfo::operator = (const LineInfo& l){
569   buf = l.buf;
570   cur_pos = l.cur_pos;
571   history_trace = l.history_trace;
572 }
573 
574 //// Procedure: Ctor
575 //inline Prompt::Prompt(
576 //  const std::string& welcome_msg,
577 //  const std::string& pmt,
578 //  const filesystem::path& path,
579 //  std::istream& in,
580 //  std::ostream& out,
581 //  std::ostream& err,
582 //  int infd
583 //):
584 //  _prompt(pmt),
585 //  _history_path(path),
586 //  _cin(in),
587 //  _cout(out),
588 //  _cerr(err),
589 //  _infd(infd)
590 //{
591 //  if(::isatty(_infd)){
592 //    _cout << welcome_msg;
593 //    _columns = _terminal_columns();
594 //    if(filesystem::exists(_history_path)){
595 //      if(std::error_code ec; not filesystem::is_regular_file(_history_path, ec)){
596 //        _cerr << "The history file is not a regular file\n";
597 //      }
598 //      else{
599 //        _load_history();
600 //      }
601 //    }
602 //  }
603 //}
604 
605 
606 // Procedure: Ctor
Prompt(const std::string & welcome_msg,const std::string & pmt,const std::filesystem::path & path,FILE * in,std::ostream & out,std::ostream & err)607 inline Prompt::Prompt(
608   const std::string& welcome_msg,
609   const std::string& pmt,
610   const std::filesystem::path& path,
611   FILE* in,
612   std::ostream& out,
613   std::ostream& err
614 
615 ):
616   _prompt(pmt),
617   _history_path(path),
618   _is(in),
619   _cout(out),
620   _cerr(err)
621 {
622   if(::isatty(::fileno(_is))){
623     _cout << welcome_msg;
624     _columns = _terminal_columns();
625     if(std::filesystem::exists(_history_path)){
626       if(std::error_code ec; not std::filesystem::is_regular_file(_history_path, ec)){
627         _cerr << "The history file is not a regular file\n";
628       }
629       else{
630         _load_history();
631       }
632     }
633   }
634 }
635 
636 
637 // Procedure: Dtor
~Prompt()638 inline Prompt::~Prompt(){
639   // Restore the original mode if has kept
640   if(_has_orig_termios){
641     //::tcsetattr(_infd, TCSAFLUSH, &_orig_termios);
642     ::tcsetattr(::fileno(_is), TCSAFLUSH, &_orig_termios);
643   }
644   if(not _history.empty()){
645     _save_history();
646   }
647 }
648 
649 // Procedure: autocomplete
650 // This function adds the word into radix tree
autocomplete(const std::string & word)651 inline void Prompt::autocomplete(const std::string& word){
652   _tree.insert(word);
653 }
654 
655 // Procedure: history_size
656 // Change the max history size
set_history_size(size_t new_size)657 inline void Prompt::set_history_size(size_t new_size){
658   _max_history_size = new_size;
659 }
660 
661 
662 // Procedure: _save_history
663 // Save history commands to a file
_save_history()664 inline void Prompt::_save_history(){
665   std::ofstream ofs(_history_path);
666   if(not ofs.good()){
667     return;
668   }
669 
670   for(const auto& c: _history){
671     ofs << c << '\n';
672   }
673   ofs.close();
674 }
675 
676 
677 // Procedure: _load_history
678 // Load history commands from a file
_load_history()679 inline void Prompt::_load_history(){
680   if(std::filesystem::exists(_history_path)){
681     std::ifstream ifs(_history_path);
682     std::string placeholder;
683     while(std::getline(ifs, placeholder)){
684       if(_history.size() == _max_history_size){
685         _history.pop_front();
686       }
687       _history.emplace_back(std::move(placeholder));
688     }
689   }
690 }
691 
692 // Procedure: _add_history
693 // Add command to history list
_add_history(const std::string & hist)694 inline void Prompt::_add_history(const std::string &hist){
695   // hist cannot be empty and cannot be the same as the last one
696   if(hist.empty() or (not _history.empty() and _history.back() == hist)){
697     return ;
698   }
699   if(_history.size() == _max_history_size){
700     _history.pop_front();
701   }
702   _history.emplace_back(hist);
703 }
704 
705 // Procedure: _stdin_not_tty
706 // Store input in a string if stdin is not from tty (from pipe or redirected file)
_stdin_not_tty(std::string & s)707 inline bool Prompt::_stdin_not_tty(std::string& s){
708   //read_line(_cin, s);  // Read until newline, CR or EOF.
709   return read_line(_is, s);
710 }
711 
712 // Procedure: _unsupported_term
713 // Check the terminal is supported or not
_unsupported_term()714 inline bool Prompt::_unsupported_term(){
715 
716   static auto _unsupported_terms = {"dumb", "cons25", "emacs"};
717 
718   if(char* term = getenv("TERM"); term == NULL){
719     return false;
720   }
721   else if(std::string_view str(term);
722           std::find(_unsupported_terms.begin(),
723                     _unsupported_terms.end() , str) != _unsupported_terms.end()){
724     return true;
725   }
726   else {
727     return false;
728   }
729 }
730 
731 // Procedure: readline
732 // This is the main entry of Prompt
readline(std::string & s)733 inline bool Prompt::readline(std::string& s) {
734   if(! ::isatty(::fileno(_is))) {
735     // not a tty, either from file or pipe; we don't limit the line size
736     if(!_stdin_not_tty(s)){
737       return false;
738     }
739 
740     // TODO: ferror precedes feof
741     return ::ferror(_is) ? false : ::feof(_is) ? false : true;
742     //return _cin.eof() ? false : true;
743   }
744   else if(_unsupported_term()){
745     //read_line(_cin, s);
746     if(!read_line(_is, s)) return false;
747     return ::ferror(_is) ? false : ::feof(_is) ? false : true;
748     //return _cin.eof() ? false : true;
749   }
750   else{
751     if(not _set_raw_mode()){
752       return {};
753     }
754     _edit_line(s);
755     _add_history(s);
756     _disable_raw_mode();
757     // TODO: what?
758     //std::cout << '\n';
759     _cout << '\n';
760     return errno == EAGAIN ? false : true;
761   }
762 }
763 
764 // Procedure: _save_orig_termios
765 // Save the original termios for recovering before exit
_save_orig_termios(int fd)766 inline bool Prompt::_save_orig_termios(int fd){
767   if(not _has_orig_termios){
768     if(::tcgetattr(fd, &_orig_termios) == -1){
769       return false;
770     }
771     _has_orig_termios = true;
772   }
773   return true;
774 }
775 
776 
777 // Procedure: _get_cursor_pos
778 // Return the current position of cursor
_get_cursor_pos()779 inline int Prompt::_get_cursor_pos(){
780   /* Report cursor location */
781   // x1b[6n : DSR - Device Status Report  https://en.wikipedia.org/wiki/ANSI_escape_code
782   // The output will be like : ESC[n;mR, where "n" is the row and "m" is the column.
783   // Try this in ur terminal : echo -en "abc \x1b[6n"
784   if(not (_cout << "\x1b[6n")){
785     return -1;
786   }
787 
788   char buf[32];
789   int cols, rows;
790 
791   /* Read the response: ESC [ rows ; cols R */
792   for(size_t i=0; i<sizeof(buf)-1; i++){
793     //if(not(_cin >> buf[i]) or buf[i] == 'R'){
794     if(buf[i]=fgetc(_is); ::ferror(_is) || buf[i] == 'R'){
795       buf[i] = '\0';
796       break;
797     }
798   }
799 
800   /* Parse it. */
801   if (static_cast<KEY>(buf[0]) != KEY::ESC or
802       buf[1] != '[' or
803       ::sscanf(buf+2, "%d;%d", &rows, &cols) != 2){
804     return -1;
805   }
806   return cols;
807 }
808 
809 
810 
811 // Procedure: _clear_screen (ctrl + l)
_clear_screen()812 inline void Prompt::_clear_screen() {
813   // https://en.wikipedia.org/wiki/ANSI_escape_code
814   // CSI n ;  m H : CUP - Cursor Position
815   // CSI n J      : Erase in Display
816   //if(_cout.write("\x1b[H\x1b[2J",7); !_cout.good()){
817   if(not (_cout << "\x1b[H\x1b[2J")){
818     /* nothing to do, just to avoid warning. */
819   }
820 }
821 
822 // Procedure: _set_raw_mode
823 // Set the fd to raw mode
_set_raw_mode()824 inline bool Prompt::_set_raw_mode(){
825   if(::isatty(::fileno(_is)) and _save_orig_termios(::fileno(_is)) == true){
826     struct termios raw;
827     raw = _orig_termios;  /* modify the original mode */
828     /* input modes: no break, no CR to NL, no parity check, no strip char,
829      * no start/stop output control. */
830     raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
831     /* output modes - disable post processing */
832     raw.c_oflag &= ~(OPOST);
833     /* control modes - set 8 bit chars */
834     raw.c_cflag |= (CS8);
835     /* local modes - choing off, canonical off, no extended functions,
836      * no signal chars (^Z,^C) */
837     raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
838     /* control chars - set return condition: min number of bytes and timer.
839      * We want read to return every single byte, without timeout. */
840     raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
841 
842     /* put terminal in raw mode after flushing */
843     if (::tcsetattr(::fileno(_is), TCSAFLUSH, &raw) >= 0){
844       return true;
845     }
846   }
847   errno = ENOTTY;
848   return false;
849 }
850 
851 
852 // Procedure: _disable_raw_mode
853 // Recover the termios to the original mode
_disable_raw_mode()854 inline void Prompt::_disable_raw_mode(){
855   if(_has_orig_termios){
856     ::tcsetattr(::fileno(_is), TCSAFLUSH, &_orig_termios);
857   }
858 }
859 
860 
861 // Procedure: _terminal_columns
862 // Get the number of columns of current line buf
_terminal_columns()863 inline size_t Prompt::_terminal_columns(){
864   // Use ioctl to get Window size
865   if(winsize ws; ::ioctl(1, TIOCGWINSZ, &ws) == -1 or ws.ws_col == 0){
866     int start = _get_cursor_pos();
867     if(start == -1 or not (_cout << "\x1b[999c")){
868       return 80;
869     }
870     int cols = _get_cursor_pos();
871     if(cols == -1){
872       return 80;
873     }
874     if(cols > start){
875       char seq[32];
876       // Move cursor back
877       ::snprintf(seq, 32, "\x1b[%dD", cols-start);
878       if(_cout.write(seq, strlen(seq)); not _cout.good()){
879         /* Fail to move cursor back*/
880       }
881     }
882     return cols;
883   }
884   else{
885     return ws.ws_col;
886   }
887 }
888 
889 
890 // Procedure: user_home
891 // Return the home folder of user
_user_home() const892 inline std::filesystem::path Prompt::_user_home() const{
893   auto home = ::getenv("HOME");
894   if(home == nullptr) {
895     home = ::getpwuid(::getuid())->pw_dir;
896   }
897   return home ? home : std::filesystem::current_path();
898 }
899 
900 
901 // Procedure: _has_read_access
902 // Check a file has read access grant to the user
_has_read_access(const std::filesystem::path & path) const903 inline bool Prompt::_has_read_access(const std::filesystem::path &path) const {
904   if(auto rval=::access(path.c_str(), F_OK); rval == 0){
905     if(rval = ::access(path.c_str(), R_OK); rval == 0){
906       return true;
907     }
908   }
909   return false;
910 }
911 
912 
913 // Procedure: _next_prefix
914 // Find the prefix among a set of strings starting from position n
_next_prefix(const std::vector<std::string> & words,const size_t n)915 inline std::string Prompt::_next_prefix(const std::vector<std::string>& words, const size_t n){
916   if(words.empty()){
917     return {};
918   }
919   else if(words.size() == 1){
920     return words[0].substr(n, words[0].size()-n);
921   }
922   else{
923     for(size_t end=n ;;++end){
924       if(words[0].size() <= end){
925         return words[0].substr(n, end-n);
926       }
927       for(size_t i=1; i<words.size(); i++){
928         if(words[i].size() <= end or words[i][end] != words[0][end]){
929           return words[i].substr(n, end-n);
930         }
931       }
932     }
933   }
934 }
935 
936 
937 // Procedure: _autocomplete_iterate_command
938 // This is the main entry for autocomplete iterating commands
_autocomplete_iterate_command()939 inline int Prompt::_autocomplete_iterate_command(){
940 
941   if(auto words = _tree.match_prefix(_line.buf); words.empty()){
942     return 0;
943   }
944   else{
945     char c {0};
946     bool stop {false};
947     for(size_t i=0; not stop;){
948       if(i < words.size()){
949         _line_save = _line;
950         _line.cur_pos = words[i].size();
951         _line.buf = words[i];
952         _refresh_single_line(_line);
953         _line = _line_save;
954       }
955       else{
956         _refresh_single_line(_line);
957       }
958 
959       //if(_cin.read(&c, 1); not _cin.good()){
960       if(c = ::fgetc(_is); ::ferror(_is)){
961         return -1;
962       }
963 
964       switch(static_cast<KEY>(c)){
965         case KEY::TAB:
966           i = (i+1) % words.size();
967           break;
968         case KEY::ESC: // refresh the same thing again
969           _refresh_single_line(_line);
970           stop = true;
971           break;
972         default:
973           if(i < words.size()){
974             _line.buf = words[i];
975             _line.cur_pos = words[i].size();
976           }
977           stop = true;
978           break;
979       }
980     }
981     return c;
982   }
983 }
984 
985 
_dump_options(const std::vector<std::string> & opts)986 inline std::string Prompt::_dump_options(const std::vector<std::string>& opts){
987   if(opts.empty()){
988     return {};
989   }
990 
991   const size_t col_width = std::max_element(opts.begin(), opts.end(),
992     [](const auto& a, const auto& b){
993       return a.size() < b.size();
994     }
995   )->size() + 4;
996 
997   auto col_num = std::max(size_t{1}, _terminal_columns() / col_width);
998 
999   std::string s;
1000   for(size_t i=0; i<opts.size(); ++i){
1001     if(i % col_num == 0) {
1002       s.append("\n\r\x1b[0K");
1003     }
1004 
1005     s.append(opts[i]);
1006 
1007     if(opts[i].size() < col_width){
1008       s.append(col_width - opts[i].size(), ' ');
1009     }
1010   }
1011   return s;
1012 }
1013 
1014 
1015 // Procedure: _autocomplete_command
1016 // This is the main entry for command autocomplete
_autocomplete_command()1017 inline void Prompt::_autocomplete_command(){
1018   if(auto words = _tree.match_prefix(_line.buf); words.empty()){
1019   }
1020   else{
1021     if(auto suffix = _next_prefix(words, _line.cur_pos); not suffix.empty()){
1022       _line.buf.insert(_line.cur_pos, suffix);
1023       _line.cur_pos += suffix.size();
1024     }
1025     if(auto s = _dump_options(words); s.size() > 0){
1026       s.append("\x1b[0K\n");
1027       _cout << s;
1028     }
1029     _refresh_single_line(_line);
1030   }
1031 }
1032 
1033 
1034 // Procedure: _files_match_prefix
1035 // Find all the files in a folder that match the prefix
_files_match_prefix(const std::filesystem::path & path) const1036 inline std::vector<std::string> Prompt::_files_match_prefix(
1037   const std::filesystem::path& path
1038 ) const {
1039   // Need to check in case path is a file in current folder (parent_path = "")
1040   auto folder = path.filename() == path ? std::filesystem::current_path() : path.parent_path();
1041   std::string prefix(path.filename());
1042 
1043   std::vector<std::string> matches;
1044   if(std::error_code ec; _has_read_access(folder) and std::filesystem::is_directory(folder, ec)){
1045     for(const auto& p: std::filesystem::directory_iterator(folder)){
1046       std::string fname {p.path().filename()};
1047       if(fname.compare(0, prefix.size(), prefix) == 0){
1048         matches.emplace_back(std::move(fname));
1049       }
1050     }
1051   }
1052   return matches;
1053 }
1054 
1055 
1056 // Procedure: _files_in_folder
1057 // List all files in a given folder
_files_in_folder(const std::filesystem::path & path) const1058 inline std::vector<std::string> Prompt::_files_in_folder(
1059   const std::filesystem::path& path
1060 ) const {
1061   auto p = path.empty() ? std::filesystem::current_path() : path;
1062   std::vector<std::string> matches;
1063   // Check permission
1064   if(_has_read_access(p)){
1065     for(const auto& p: std::filesystem::directory_iterator(p)){
1066       matches.emplace_back(p.path().filename());
1067     }
1068   }
1069   return matches;
1070 }
1071 
1072 
1073 // Procedure: _dump_files
1074 // Format the strings for pretty print in terminal.
_dump_files(const std::vector<std::string> & v,const std::filesystem::path & path)1075 inline std::string Prompt::_dump_files(
1076   const std::vector<std::string>& v,
1077   const std::filesystem::path& path)
1078 {
1079   if(v.empty()){
1080     return {};
1081   }
1082 
1083   const size_t col_width = std::max_element(v.begin(), v.end(),
1084     [](const auto& a, const auto& b){
1085       return a.size() < b.size();
1086     }
1087   )->size() + 4;
1088 
1089   auto col_num = std::max(size_t{1}, _terminal_columns() / col_width);
1090 
1091   std::string s;
1092 
1093   char seq[64];
1094   snprintf(seq, 64, "\033[%d;1;49m", static_cast<int>(COLOR::BLUE));
1095   std::error_code ec;
1096   for(size_t i=0; i<v.size(); ++i){
1097 
1098     if(i % col_num == 0) {
1099       s.append("\n\r\x1b[0K");
1100     }
1101 
1102     if(std::filesystem::is_directory(path.native() + "/" + v[i], ec)){
1103       // A typical color code example : \033[31;1;4m
1104       //   \033[ : begin of color code, 31 : red color,  1 : bold,  4 : underlined
1105       s.append(seq, strlen(seq));
1106       s.append(v[i]);
1107       s.append("\033[0m");
1108     }
1109     else{
1110       s.append(v[i]);
1111     }
1112     s.append(col_width - v[i].size(), ' ');
1113   }
1114   return s;
1115 }
1116 
1117 // Procedure: _autocomplete_folder
1118 // The entry for folder autocomplete
_autocomplete_folder()1119 inline void Prompt::_autocomplete_folder(){
1120   std::string s;
1121   size_t ws_index = _line.buf.rfind(' ', _line.cur_pos) + 1;
1122 
1123   std::filesystem::path p(
1124     _line.buf[ws_index] != '~' ?
1125     _line.buf.substr(ws_index, _line.cur_pos - ws_index) :
1126     _user_home().native() + _line.buf.substr(ws_index+1, _line.cur_pos-ws_index-1)
1127   );
1128 
1129   if(std::error_code ec; p.empty() or std::filesystem::is_directory(p, ec)) {
1130     s = _dump_files(_files_in_folder(p), p);
1131   }
1132   else{
1133     auto match = _files_match_prefix(p);
1134     if(not match.empty()){
1135       s = _dump_files(match, p.parent_path());
1136       if(auto suffix = _next_prefix(match, p.filename().native().size()); not suffix.empty()){
1137         _line.buf.insert(_line.cur_pos, suffix);
1138         _line.cur_pos += suffix.size();
1139         p += suffix;
1140         // Append a '/' if is a folder and not the prefix of other files
1141         if(std::filesystem::is_directory(p, ec) and
1142             std::count_if(match.begin(), match.end(),
1143               [p = p.filename().native()](const auto& m)
1144               { return (m.size() >= p.size() and m.compare(0, p.size(), p) == 0); }) == 1){
1145           _line.buf.insert(_line.cur_pos, 1, '/');
1146           _line.cur_pos += 1;
1147         }
1148       }
1149     }
1150   }
1151   if(s.size() > 0){
1152     s.append("\x1b[0K\n");
1153     _cout << s;
1154   }
1155 
1156   _refresh_single_line(_line);
1157 }
1158 
1159 // Procedure: _key_delete_prev_word
1160 // Remove the word before cursor (including the whitespace between cursor and the word)
_key_delete_prev_word(LineInfo & line)1161 inline void Prompt::_key_delete_prev_word(LineInfo& line){
1162   // Remove all whitespace before cursor
1163   auto iter = std::find_if_not(line.buf.rbegin()+line.buf.size()-line.cur_pos, line.buf.rend(),
1164                                [](const auto& c){ return c == ' ';});
1165   auto offset = std::distance(iter.base(), line.buf.begin()+line.cur_pos);
1166   line.buf.erase(iter.base(), line.buf.begin()+line.cur_pos);
1167   line.cur_pos -= offset;
1168 
1169   // Remove the word before cursor and stop at the first ws
1170   iter = std::find_if_not(line.buf.rbegin()+line.buf.size()-line.cur_pos, line.buf.rend(),
1171                            [](const auto& c){ return c != ' ';});
1172   offset = std::distance(iter.base(), line.buf.begin()+line.cur_pos);
1173   line.buf.erase(iter.base(), line.buf.begin()+line.cur_pos);
1174   line.cur_pos -= offset;
1175 }
1176 
1177 // Procedure: _key_delete
1178 // Remove the character at cursor
_key_delete(LineInfo & line)1179 inline void Prompt::_key_delete(LineInfo &line){
1180   if(line.buf.size() > 0){
1181     line.buf.erase(line.cur_pos, 1);
1182   }
1183 }
1184 
1185 // Procedure: _key_backspace
1186 // Remove the character before curosr
_key_backspace(LineInfo & line)1187 inline void Prompt::_key_backspace(LineInfo &line){
1188   if(line.cur_pos > 0){
1189     line.buf.erase(line.cur_pos-1, 1);
1190     line.cur_pos--;
1191   }
1192 }
1193 
1194 // Procedure: _key_prev_history
1195 // Set line buffer to previous command in history
_key_prev_history(LineInfo & line)1196 inline void Prompt::_key_prev_history(LineInfo& line){
1197   _key_history(line, true);
1198 }
1199 
1200 // Procedure: _key_next_history
1201 // Set line buffer to next command in history
_key_next_history(LineInfo & line)1202 inline void Prompt::_key_next_history(LineInfo& line){
1203   _key_history(line, false);
1204 }
1205 
1206 // Procedure:_key_history
1207 // Set the line buffer to previous/next history command
_key_history(LineInfo & line,bool prev)1208 inline void Prompt::_key_history(LineInfo &line, bool prev){
1209   if(_history.size() > 1){
1210     *std::next(_history.begin(), _history.size()-1-line.history_trace) = line.buf;
1211 
1212     if(line.history_trace += prev ? 1 : -1; line.history_trace < 0){
1213       line.history_trace = 0;
1214     }
1215     else if(line.history_trace >= static_cast<int>(_history.size())){
1216       line.history_trace = _history.size()-1;
1217     }
1218     else{
1219       auto it = std::next(_history.begin(), _history.size()-1-line.history_trace);
1220       line.buf = *it;
1221       line.cur_pos = line.buf.size();
1222     }
1223   }
1224 }
1225 
1226 // Procedure: _append_character
1227 // Insert a character to the cursor position in line buffer
_append_character(LineInfo & line,char c)1228 inline bool Prompt::_append_character(LineInfo& line, char c){
1229   try{
1230     line.buf.insert(line.cur_pos, 1, c);
1231   }
1232   catch (const std::exception& e){
1233     _cerr << "append failed: " << e.what() << '\n';
1234     assert(false);
1235   }
1236 
1237   if(line.cur_pos++; line.buf.size() == line.cur_pos){
1238     if(not (_cout << c)){
1239       return false;
1240     }
1241   }
1242   else{
1243     _refresh_single_line(line);
1244   }
1245   return true;
1246 }
1247 
1248 // Procedure: _key_handle_CSI
1249 // Handle Control Sequence Introducer
_key_handle_CSI(LineInfo & line)1250 inline bool Prompt::_key_handle_CSI(LineInfo& line){
1251   char seq[3];
1252   //if(_cin.read(seq,1), _cin.read(seq+1,1); not _cin.good() ){
1253   if(seq[0] = ::fgetc(_is), seq[1] = ::fgetc(_is); ::ferror(_is)){
1254     return false;
1255   }
1256   if(seq[0] == '['){
1257     if(seq[1] >= '0' and seq[1] <= '9'){
1258       //if(_cin.read(seq+2, 1); not _cin.good()){
1259       if(seq[2] = ::fgetc(_is); ::ferror(_is)){
1260         return false;
1261       }
1262       if(seq[2] == '~' and seq[1] == '3'){
1263         _key_delete(line);
1264       }
1265     }
1266     else{
1267       switch(seq[1]){
1268         case 'A':  // Prev history
1269           _key_prev_history(line);
1270           break;
1271         case 'B':  // Next history
1272           _key_next_history(line);
1273           break;
1274         case 'C':  // Move cursor to right
1275           if(line.cur_pos != line.buf.size()){
1276             line.cur_pos ++;
1277           }
1278           break;
1279         case 'D':  // Move cursor to left
1280           if(line.cur_pos > 0){
1281             line.cur_pos --;
1282           }
1283           break;
1284         case 'H':  // Home : move cursor to the starting
1285           line.cur_pos = 0;
1286           break;
1287         case 'F':  // End  : move cursor to the end
1288           line.cur_pos = line.buf.size();
1289           break;
1290       }
1291     }
1292   }
1293   else if(seq[0] == 'O'){
1294     switch(seq[1]){
1295       case 'H':  // Home : move cursor to the starting
1296         line.cur_pos = 0;
1297         break;
1298       case 'F':  // End  : move cursor to the end
1299         line.cur_pos = line.buf.size();
1300         break;
1301     }
1302   }
1303   return true;
1304 }
1305 
1306 
1307 // Procedure: _edit_line
1308 // Handle the character input from the user
_edit_line(std::string & s)1309 inline void Prompt::_edit_line(std::string &s){
1310   if(not (_cout << _prompt)){
1311     return;
1312   }
1313 
1314   _history.emplace_back("");
1315   _line.reset();
1316   s.clear();
1317   for(char c;;){
1318     //if(_cin.read(&c, 1); not _cin.good()){
1319     if(c = ::fgetc(_is); ::ferror(_is)){
1320       s = _line.buf;
1321       return ;
1322     }
1323 
1324     // if user hits tab
1325     if(static_cast<KEY>(c) == KEY::TAB){
1326       if(_line.buf.empty()){
1327         continue;
1328       }
1329       else if(_line.buf.rfind(' ', _line.cur_pos) != std::string::npos){
1330         _autocomplete_folder();
1331         continue;
1332       }
1333       else{
1334         _autocomplete_command();
1335         continue;
1336         //if(c=_autocomplete_iterate_command(); c<0){
1337         //if(c=_autocomplete_command(); c<0){
1338         //  // something wrong happened
1339         //  return;
1340         //}
1341         //else if(c == 0){
1342         // continue;
1343         //}
1344       }
1345     }
1346 
1347     // Proceed to process character
1348     switch(static_cast<KEY>(c)){
1349       case KEY::ENTER:
1350         _history.pop_back(); // Remove the emptry string history added in beginning
1351         s =  _line.buf;
1352         return ;
1353       case KEY::CTRL_A:    // Go to the start of the line
1354         if(_line.cur_pos != 0){
1355           _line.cur_pos = 0;
1356         }
1357         _refresh_single_line(_line);
1358         break;
1359       case KEY::CTRL_B:    // Move cursor to left
1360         if(_line.cur_pos > 0){
1361           _line.cur_pos --;
1362         }
1363         _refresh_single_line(_line);
1364         break;
1365       case KEY::CTRL_C:
1366         _history.pop_back(); // Remove the emptry string history added in beginning
1367         errno = EAGAIN;
1368         return ;
1369       case KEY::CTRL_D:    // Remove the char at the right of cursor.
1370                            // If the line is empty, act as end-of-file
1371         if(_line.buf.size() > 0){
1372           _key_delete(_line);
1373           _refresh_single_line(_line);
1374         }
1375         else{
1376           _history.pop_back();
1377           return;
1378         }
1379         break;
1380       case KEY::CTRL_E:    // Move cursor to end of line
1381         if(_line.cur_pos != _line.buf.size()){
1382           _line.cur_pos = _line.buf.size();
1383         }
1384          _refresh_single_line(_line);
1385         break;
1386       case KEY::CTRL_F:    // Move cursor to right
1387         if(_line.cur_pos != _line.buf.size()){
1388           _line.cur_pos ++;
1389         }
1390         _refresh_single_line(_line);
1391         break;
1392       case KEY::BACKSPACE:  // CTRL_H is the same as BACKSPACE
1393       case KEY::CTRL_H:
1394         _key_backspace(_line);
1395         _refresh_single_line(_line);
1396         break;
1397       case KEY::CTRL_K:    // Delete from current to EOF
1398         _line.buf.erase(_line.cur_pos, _line.buf.size()-_line.cur_pos);
1399         _refresh_single_line(_line);
1400         break;
1401       case KEY::CTRL_L:    // Clear screen
1402         _clear_screen();
1403         _refresh_single_line(_line);
1404         break;
1405       case KEY::CTRL_N:    // Next history command
1406         _key_next_history(_line);
1407         _refresh_single_line(_line);
1408         break;
1409       case KEY::CTRL_P:    // Previous history command
1410         _key_prev_history(_line);
1411         _refresh_single_line(_line);
1412         break;
1413       case KEY::CTRL_T:    // Swap current char with previous
1414         if(_line.cur_pos > 0){
1415           std::swap(_line.buf[_line.cur_pos], _line.buf[_line.cur_pos-1]);
1416           if(_line.cur_pos < _line.buf.size()){
1417             _line.cur_pos ++;
1418           }
1419           _refresh_single_line(_line);
1420         }
1421         break;
1422       case KEY::CTRL_U:    // Delete whole line
1423         _line.cur_pos = 0;
1424         _line.buf.clear();
1425         _refresh_single_line(_line);
1426         break;
1427       case KEY::CTRL_W:    // Delete previous word
1428         _key_delete_prev_word(_line);
1429         _refresh_single_line(_line);
1430         break;
1431       case KEY::ESC:
1432         if(not _key_handle_CSI(_line)){
1433           _history.pop_back(); // Remove the emptry string history added in beginning
1434           return;
1435         }
1436         _refresh_single_line(_line);
1437         break;
1438       default:
1439         _append_character(_line, c);
1440         break;
1441     }
1442   }
1443 }
1444 
1445 
1446 // Procedure: _refresh_single_line
1447 // Flush the current line buffer on screen
_refresh_single_line(LineInfo & l)1448 inline void Prompt::_refresh_single_line(LineInfo &l){
1449   // 1. Append "move cursor to left" in the output buffer
1450   // 2. Append buf to output buffer
1451   // 3. Append "erase to  the right" to the output buffer
1452   // 4. Append "forward cursor" to the output buffer : Adjust cursor to correct pos
1453   // 5. Write output buffer to fd
1454 
1455   static const std::string CR {"\r"};       // Carriage Return (set cursor to left)
1456   static const std::string EL {"\x1b[0K"};  // Erase in Line (clear from cursor to the end of the line)
1457 
1458   auto len {l.buf.size()};
1459   auto pos {l.cur_pos};
1460   size_t start {0};
1461 
1462   if(_prompt.length()+pos >= _columns){
1463     start += _prompt.length()+pos-_columns-1;
1464     len -= _prompt.length()+pos-_columns-1;
1465     pos += (_prompt.length()+pos-_columns-1);
1466   }
1467 
1468   if(_prompt.length()+len > _columns){
1469     len -= (_prompt.length()+len-_columns);
1470   }
1471 
1472   char seq[64];
1473   ::snprintf(seq, 64, "\r\x1b[%dC", (int)(pos+_prompt.length()));
1474 
1475   _obuf.clear();
1476   _obuf.reserve(CR.length()+_prompt.length()+len+EL.length()+strlen(seq));
1477   _obuf.append(CR);
1478   _obuf.append(_prompt);
1479   _obuf.append(l.buf.data() + start, len);
1480   _obuf.append(EL);
1481   _obuf.append(seq, strlen(seq));
1482 
1483   if(not (_cout << _obuf)){
1484     _cerr << "Refresh line fail\n";
1485   }
1486 }
1487 
1488 };  // end of namespace prompt. -------------------------------------------------------------------
1489 
1490 #endif
1491 
1492