Editor.cc
Go to the documentation of this file.
1 //
2 // Copyright (c) 2020 Fraunhofer Institute for Applied Information Technology (FIT)
3 // Network Research Group (NET)
4 // Schloss Birlinghoven, 53754 Sankt Augustin, GERMANY
5 // Contact: support@wiback.org
6 //
7 // This file is part of the SENF code tree.
8 // It is licensed under the 3-clause BSD License (aka New BSD License).
9 // See LICENSE.txt in the top level directory for details or visit
10 // https://opensource.org/licenses/BSD-3-Clause
11 //
12 
13 
17 #include "Editor.hh"
18 //#include "Editor.ih"
19 
20 // Custom includes
21 #include <senf/Utils/membind.hh>
23 
24 //#include "Editor.mpp"
25 #define prefix_
26 //-/////////////////////////////////////////////////////////////////////////////////////////////////
27 
29  : terminal_ (&terminal),
30  keyTimeout_ (ClockService::milliseconds(DEFAULT_KEY_TIMEOUT_MS)),
31  timer_ ("senf::term::BaseEditor::keySequenceTimeout",
32  membind(&BaseEditor::keySequenceTimeout, this)),
33  column_ (0u), displayHeight_ (1u), line_ (0u)
34 {
35  terminal_->setCallbacks(*this);
36 }
37 
39 {
40  reset();
41  write("\n");
43  column_ = 0;
44 }
45 
47 {
48  if (c >= width())
49  c = width();
50  if (c > column_) {
52  write(tifo_.formatString(Terminfo::properties::ParmRightCursor, c - column_));
53  column_ = c;
54  }
55  else {
56  char const * cuf1 (tifo_.getString(Terminfo::properties::CursorRight));
57  while (c > column_) {
58  write(cuf1);
59  ++column_;
60  }
61  }
62  }
63  else if (c < column_) {
65  write(tifo_.formatString(Terminfo::properties::ParmLeftCursor, column_ - c));
66  column_ = c;
67  }
68  else {
69  char const * cub1 (tifo_.getString(Terminfo::properties::CursorLeft));
70  while (c < column_) {
71  write(cub1);
72  --column_;
73  }
74  }
75  }
76 }
77 
79 {
80  if (column_ >= width()-1)
81  return;
82  write(ch);
83  ++ column_;
84 }
85 
86 prefix_ void senf::term::BaseEditor::put(std::string const & text)
87 {
88  if (text.size() > width()-column_-1) {
89  write(text.substr(0,width()-column_-1));
90  column_ = width() - 1;
91  }
92  else {
93  write(text);
94  column_ += text.size();
95  }
96 }
97 
99 {
100  write("\r");
102  column_ = 0;
103 }
104 
106 {
110 }
111 
113 {
117 }
118 
120 {
123 }
124 
126 {
127  if (l >= height())
128  l = height() - 1;
129  unsigned ll (l);
130  if (ll >= displayHeight_)
131  ll = displayHeight_-1;
132  if (ll > line_) {
134  write(tifo_.formatString(Terminfo::properties::ParmDownCursor, ll - line_));
135  line_ = ll;
136  }
137  else {
138  char const * cud1 (tifo_.getString(Terminfo::properties::CursorDown));
139  while (ll > line_) {
140  write(cud1);
141  ++line_;
142  }
143  }
144  }
145  else if (ll < line_) {
147  write(tifo_.formatString(Terminfo::properties::ParmUpCursor, line_ - ll));
148  line_ = ll;
149  }
150  else {
151  char const * cuu1 (tifo_.getString(Terminfo::properties::CursorUp));
152  while (ll < line_) {
153  write(cuu1);
154  --line_;
155  }
156  }
157  }
158  while (line_ < l) {
159  write("\n");
161  ++displayHeight_;
162  ++line_;
163  }
164  write('\r');
165  column_ = 0;
166 }
167 
169 {
170  for (unsigned i (1); i < displayHeight_; ++i) {
171  toLine(i);
172  clearLine();
173  }
174  toLine(0);
175  displayHeight_ = 1;
176 }
177 
179  const
180 {
181  return column_;
182 }
183 
185  const
186 {
187  return line_;
188 }
189 
191 {
192  try {
193  tifo_.load(terminal_->terminalType());
194  keyParser_.load(tifo_);
195  }
197  return false;
198  }
199 
200  typedef Terminfo::properties p;
201  if (! (tifo_.hasProperty(p::ClrEol) &&
202  (tifo_.hasProperty(p::ParmRightCursor) || tifo_.hasProperty(p::CursorRight)) &&
203  (tifo_.hasProperty(p::ParmLeftCursor) || tifo_.hasProperty(p::CursorLeft))))
204  return false;
205 
208  return true;
209 }
210 
211 prefix_ void senf::term::BaseEditor::cb_charReceived(char c)
212 {
213  inputBuffer_ += c;
214  timer_.timeout(scheduler::eventTime() + keyTimeout_);
215  processKeys();
216 }
217 
219 {
220  if (column_ >= width())
221  column_ = width()-1;
222 }
223 
224 prefix_ void senf::term::BaseEditor::keySequenceTimeout()
225 {
226  while (!inputBuffer_.empty()) {
227  processKeys();
228  v_keyReceived(keycode_t(inputBuffer_[0]));
229  inputBuffer_.erase(0, 1);
230  }
231 }
232 
233 prefix_ void senf::term::BaseEditor::processKeys()
234 {
235  do {
236  std::pair<KeyParser::keycode_t, std::string::size_type> result
237  (keyParser_.lookup(inputBuffer_));
238  if (result.first == KeyParser::Incomplete)
239  return;
240  v_keyReceived(result.first);
241  inputBuffer_.erase(0, result.second);
242  } while (! inputBuffer_.empty());
243  timer_.disable();
244 }
245 
247  const
248 {
249  return terminal_->width();
250 }
251 
253  const
254 {
255  return terminal_->height();
256 }
257 
258 prefix_ void senf::term::BaseEditor::write(char ch)
259 {
260  terminal_->write(ch);
261 }
262 
263 prefix_ void senf::term::BaseEditor::write(std::string const & s)
264 {
265  for (std::string::const_iterator i (s.begin()); i != s.end(); ++i)
266  write(*i);
267 }
268 
269 //-/////////////////////////////////////////////////////////////////////////////////////////////////
270 
272  : BaseEditor(terminal), enabled_ (false), redisplayNeeded_ (false), prompt_ ("$"),
273  promptWidth_ (1u), editWidth_ (0u), text_ (""), point_ (0u), displayPos_ (0u),
274  lastKey_ (0u), callback_ (cb), historyPoint_ (0u)
275 {
291 }
292 
293 prefix_ void senf::term::LineEditor::prompt(std::string const & text)
294 {
295  prompt_ = text;
296  promptWidth_ = prompt_.size();
297  if (promptWidth_ > width() - 4 && width() > 4)
298  promptWidth_ = width() - 4;
299  editWidth_ = width() - promptWidth_ - 3;
300  if (enabled_)
301  redisplay();
302 }
303 
304 prefix_ void senf::term::LineEditor::set(std::string const & text, unsigned pos)
305 {
306  text_ = text;
307  point_ = pos;
308  if (point_ > text.size())
309  point_ = text.size();
310  displayPos_ = 0u;
311  if (point_ > editWidth_)
312  displayPos_ = point_ - editWidth_;
313  redisplay();
314 }
315 
317 {
318  if (enabled_)
319  return;
320  enabled_ = true;
321  for (unsigned n (0); n < auxDisplay_.size(); ++n) {
322  toLine(n+1);
323  put(auxDisplay_[n]);
324  }
325  toLine(0);
326  forceRedisplay();
327 }
328 
330 {
331  if (! enabled_)
332  return;
333  reset();
334  clearLine();
335  enabled_ = false;
336 }
337 
339 {
340  if (enabled_)
341  newline();
342  hide();
343  pushHistory(text_, true);
344  callback_(text_);
345  clear();
346 }
347 
349 {
350  set("");
351  historyPoint_ = history_.size();
352 }
353 
355 {
356  redisplayNeeded_ = true;
357 }
358 
360 {
361  if (! enabled_)
362  return;
363  clearLine();
364  setBold();
365  if (prompt_.size() > promptWidth_)
366  put(prompt_.substr(prompt_.size()-promptWidth_));
367  else
368  put(prompt_);
369  put( displayPos_ > 0 ? '<' : ' ' );
370  if (text_.size() > displayPos_ + editWidth_) {
371  toColumn(editWidth_ + promptWidth_ + 1);
372  put('>');
373  toColumn(promptWidth_ + 1);
374  }
375  setNormal();
376  put(text_.substr(displayPos_, editWidth_));
377  toColumn(point_ - displayPos_ + promptWidth_ + 1);
378  redisplayNeeded_ = false;
379 }
380 
382 {
383  point_ = n;
384  if (point_ > text_.size())
385  point_ = text_.size();
386  if (point_ < displayPos_)
387  displayPos_ = point_;
388  if (point_ > displayPos_+editWidth_)
389  displayPos_ = point_-editWidth_;
390  redisplay();
391 }
392 
394 {
395  displayPos_ = n;
396  if (displayPos_ > text_.size())
397  displayPos_ = text_.size();
398  if (point_ < displayPos_)
399  point_ = displayPos_;
400  if (point_ > displayPos_+editWidth_)
401  point_ = displayPos_+editWidth_;
402  redisplay();
403 }
404 
406 {
407  if (point_ >= text_.size())
408  return;
409  text_.erase(point_, n);
410  redisplay();
411 }
412 
414 {
415  text_.insert(point_, std::string(1, ch));
416  gotoChar(point_+1);
417  redisplay();
418 }
419 
420 prefix_ void senf::term::LineEditor::insert(std::string const & text)
421 {
422  text_.insert(point_, text);
423  gotoChar(point_+text.size());
424  redisplay();
425 }
426 
428 {
429  if (! text.empty()
430  && (accept || historyPoint_ == history_.size() || history_[historyPoint_] != text)
431  && (history_.empty() || history_.back() != text)) {
432  history_.push_back(text);
433  while (history_.size() > MAX_HISTORY_SIZE)
434  history_.erase(history_.begin());
435  if (accept)
436  historyPoint_ = history_.size() - 1;
437  }
438 }
439 
441 {
442  if (historyPoint_ <= 0)
443  return;
444  pushHistory(text_);
445  std::string entry (history_[--historyPoint_]);
446  set(entry, entry.size());
447 }
448 
450 {
451  if (historyPoint_ >= history_.size())
452  return;
453  pushHistory(text_);
454  ++ historyPoint_;
455  if (historyPoint_ >= history_.size())
456  set("");
457  else {
458  std::string entry (history_[historyPoint_]);
459  set(entry, entry.size());
460  }
461 }
462 
463 prefix_ void senf::term::LineEditor::auxDisplay(unsigned line, std::string const & text)
464 {
465  toLine(line+1);
466  clearLine();
467  put(text);
468  while (auxDisplay_.size() < line+1)
469  auxDisplay_.push_back("");
470  auxDisplay_[line] = text;
471 }
472 
474 {
475  return height()-1;
476 }
477 
479 {
480  reset();
481  auxDisplay_.clear();
482 }
483 
485 {
486  return text_;
487 }
488 
490 {
491  return point_;
492 }
493 
495 {
496  return displayPos_;
497 }
498 
500 {
501  return lastKey_;
502 }
503 
505 {
506  bindings_[key] = binding;
507 }
508 
510 {
511  bindings_.erase(key);
512 }
513 
514 prefix_ bool senf::term::LineEditor::cb_init()
515 {
516  if (!BaseEditor::cb_init())
517  return false;
518  prompt(prompt_);
519  show();
520  return true;
521 }
522 
523 prefix_ void senf::term::LineEditor::cb_windowSizeChanged()
524 {
526  clearAuxDisplay();
527  prompt(prompt_);
528  gotoChar(point_);
529  forceRedisplay();
530 }
531 
532 prefix_ void senf::term::LineEditor::v_keyReceived(keycode_t key)
533 {
534  if (! enabled_)
535  return;
536  clearAuxDisplay();
537  lastKey_ = key;
538  KeyMap::iterator i (bindings_.find(key));
539  if (i != bindings_.end())
540  i->second(*this);
541  else if (key >= ' ' && key < 256)
542  insert(char(key));
543  if (currentLine() != 0)
544  toLine(0);
545  if (redisplayNeeded_)
546  forceRedisplay();
547  else
548  toColumn(point_ - displayPos_ + promptWidth_ + 1);
549 }
550 
551 //-/////////////////////////////////////////////////////////////////////////////////////////////////
552 
554 {
555  LineEditor::keycode_t key (editor.lastKey());
556  if (key >= ' ' && key < 256)
557  editor.insert(key);
558 }
559 
561 {
562  editor.gotoChar(editor.point()+1);
563 }
564 
566 {
567  unsigned p (editor.point());
568  if (p>0)
569  editor.gotoChar(p-1);
570 }
571 
573 {
574  editor.accept();
575 }
576 
578 {
579  if (editor.text().empty()) {
580  editor.prevHistory();
581  editor.forceRedisplay();
582  }
583  editor.accept();
584 }
585 
587 {
588  unsigned p (editor.point());
589  if (p>0) {
590  editor.gotoChar(p-1);
591  editor.deleteChar();
592  }
593 }
594 
596 {
597  editor.deleteChar();
598 }
599 
601 {
602  editor.gotoChar(0u);
603 }
604 
606 {
607  editor.gotoChar(editor.text().size());
608 }
609 
611 {
612  editor.deleteChar(editor.text().size()-editor.point());
613 }
614 
616 {
617  editor.newline();
618  editor.clear();
619  editor.redisplay();
620 }
621 
623 {
624  editor.prevHistory();
625 }
626 
628 {
629  editor.nextHistory();
630 }
631 
633 {
634  editor.maybeClrScr();
635  editor.clearLine();
636  editor.forceRedisplay();
637 }
638 
640 {
641  typedef std::vector<std::string> Completions;
642 
643  std::string text (editor.text());
644  Completions completions;
645  unsigned b (0);
646  unsigned e (editor.point());
647  std::string prefix;
648  completer(editor, b, e, prefix, completions);
649  if (completions.empty())
650  return;
651  if (e > text.size())
652  e = text.size();
653  if (b > e)
654  b = e;
655 
656  // Find common start string of all completions
657  unsigned commonStart (completions[0].size());
658  unsigned maxLen (commonStart);
659  for (Completions::const_iterator i (boost::next(completions.begin()));
660  i != completions.end(); ++i) {
661  if (i->size() > maxLen)
662  maxLen = i->size();
663  unsigned n (0u);
664  for (; n < commonStart && n < i->size() && completions[0][n] == (*i)[n]; ++n) ;
665  commonStart = n;
666  }
667 
668  // Replace to-be-completed string with the common start string shared by all completions
669  std::string completion (prefix+completions[0].substr(0, commonStart));
670  bool didComplete (false);
671  if (text.substr(b, e) != completion) {
672  text.erase(b, e);
673  text.insert(b, completion);
674  didComplete = true;
675  }
676 
677  // Otherwise place cursor directly after the (possibly partial) completion
678  editor.set(text, b+prefix.size()+commonStart);
679  if (didComplete || completions.size() == 1)
680  return;
681 
682  // Text was not changed, show list of possible completions
683  unsigned colWidth (maxLen+2);
684  unsigned nColumns ((editor.width()-1) / colWidth);
685  if (nColumns < 1) nColumns = 1;
686  unsigned nRows ((completions.size()+nColumns-1) / nColumns);
687  if (nRows > editor.maxAuxDisplayHeight()) {
688  editor.auxDisplay(0, "(too many completions)");
689  return;
690  }
691  Completions::iterator i (completions.begin());
692  for (unsigned row (0); row < nRows; ++row) {
693  std::string line;
694  for (unsigned column (0); column < nColumns && i != completions.end(); ++column, ++i) {
695  std::string entry (colWidth, ' ');
696  std::copy(i->begin(),
697  i->size() > colWidth-2 ? i->begin()+colWidth-2 : i->end(),
698  entry.begin());
699  line += entry;
700  }
701  editor.auxDisplay(row, line);
702  }
703 }
704 
705 //-/////////////////////////////////////////////////////////////////////////////////////////////////
706 #undef prefix_
707 //#include "Editor.mpp"
708 
709 
710 // Local Variables:
711 // mode: c++
712 // fill-column: 100
713 // comment-column: 40
714 // c-file-style: "senf"
715 // indent-tabs-mode: nil
716 // ispell-local-dictionary: "american"
717 // compile-command: "scons -u test"
718 // End:
unsigned currentLine() const
Return number of current relative line.
Definition: Editor.cc:184
Provide editor support terminal functionality.
Definition: Editor.hh:51
void load(std::string const &term)
Load terminfo entry term.
Definition: Terminfo.cc:145
void nextHistory()
Switch to next history entry.
Definition: Editor.cc:449
void acceptWithRepeat(LineEditor &editor)
Accept, possibly repeat last history entry.
Definition: Editor.cc:577
std::pair< keycode_t, size_type > lookup(std::string const &key) const
Lookup up string key.
Definition: Terminfo.cc:579
void clear()
Clear editor buffer.
Definition: Editor.cc:348
void deleteChar(unsigned n=1)
Delete n characters at point.
Definition: Editor.cc:405
void defineKey(keycode_t key, KeyBinding binding)
Bind key key to binding.
Definition: Editor.cc:504
boost::function< void(LineEditor &, unsigned &b, unsigned &e, std::string &prefix, std::vector< std::string > &)> Completer
Definition: Editor.hh:344
void redisplay()
Mark the editor buffer for redisplay.
Definition: Editor.cc:354
void auxDisplay(unsigned line, std::string const &text)
Display text on aux display line line.
Definition: Editor.cc:463
bool hasProperty(properties::Boolean p) const
true, if boolean property p exists
Definition: Terminfo.cc:183
unsigned height() const
Return current screen height.
Definition: Editor.cc:252
void selfInsertCommand(LineEditor &editor)
Insert key as literal character.
Definition: Editor.cc:553
void accept()
Accept current user input and call the accept callback.
Definition: Editor.cc:338
virtual void cb_windowSizeChanged()
Called whenever the terminal window size changes.
Definition: Editor.cc:218
void nextHistory(LineEditor &editor)
Move to next history entry.
Definition: Editor.cc:627
virtual unsigned height() const =0
Get current terminal window height.
void clearLine()
Clear current line and move cursor to first column.
Definition: Editor.cc:98
void unsetKey(keycode_t key)
Remove all bindings for key.
Definition: Editor.cc:509
boost::function< R(Args)> membind(R(T::*fn)(Args), T *ob)
void gotoChar(unsigned n)
Move cursor to position n.
Definition: Editor.cc:381
void restartEdit(LineEditor &editor)
Clear edit buffer and restart edit.
Definition: Editor.cc:615
void toLine(unsigned l)
Move to relative display line l.
Definition: Editor.cc:125
void prevHistory()
Switch to previous history entry.
Definition: Editor.cc:440
void scrollTo(unsigned n)
Move position to beginning of display line.
Definition: Editor.cc:393
static unsigned const MAX_HISTORY_SIZE
Definition: Editor.hh:213
void backwardChar(LineEditor &editor)
Move one char backwards.
Definition: Editor.cc:565
void insert(char ch)
Insert ch at point.
Definition: Editor.cc:413
BaseEditor(AbstractTerminal &terminal)
Definition: Editor.cc:28
virtual void v_keyReceived(keycode_t key)=0
Called whenever a key is received.
void accept(LineEditor &editor)
Accept input line.
Definition: Editor.cc:572
void complete(LineEditor &editor, Completer completer)
Complete text at cursor.
Definition: Editor.cc:639
void deleteToEndOfLine(LineEditor &editor)
Delete from cursor to end of line.
Definition: Editor.cc:610
Invalid, incomplete or non-existent terminfo entry exception.
Definition: Terminfo.hh:206
void backwardDeleteChar(LineEditor &editor)
Delete char before cursor.
Definition: Editor.cc:586
virtual bool cb_init()
Called when terminal is initialized.
Definition: Editor.cc:190
string_t getString(properties::String p) const
Get string property value.
Definition: Terminfo.cc:175
void prompt(std::string const &text)
Set prompt string.
Definition: Editor.cc:293
LineEditor(AbstractTerminal &terminal, AcceptCallback cb)
Create a LineEditor.
Definition: Editor.cc:271
Editor public header.
void forwardChar(LineEditor &editor)
Move one char forward.
Definition: Editor.cc:560
unsigned displayPos()
Get current display position.
Definition: Editor.cc:494
void put(char ch)
Write ch at current column.
Definition: Editor.cc:78
void timeout(ClockService::clock_type const &timeout, bool initiallyEnabled=true)
virtual void write(char ch)=0
Write character to terminal.
std::string const & text()
Get current editor buffer contents.
Definition: Editor.cc:484
void hide()
Disable editor widget.
Definition: Editor.cc:329
boost::function< void(std::string const &)> AcceptCallback
Callback function type.
Definition: Editor.hh:210
void prevHistory(LineEditor &editor)
Move to previous history entry.
Definition: Editor.cc:622
void forceRedisplay()
Redisplay the editor buffer now.
Definition: Editor.cc:359
static keycode_t Ctrl(char ch)
Helper to convert uppercase char to Control key code.
Definition: Terminfo.hh:267
void setNormal()
Set normal char display.
Definition: Editor.cc:112
unsigned currentColumn() const
Return number of current column.
Definition: Editor.cc:178
Terminfo property constants.
Definition: Terminfo.hh:69
void pushHistory(std::string const &text, bool accept=false)
Add string text to history.
Definition: Editor.cc:427
virtual std::string terminalType()=0
Get the terminal type.
boost::function< void(LineEditor &)> KeyBinding
Type of a key binding function.
Definition: Editor.hh:209
void show()
Enable editor widget.
Definition: Editor.cc:316
unsigned maxAuxDisplayHeight()
Get maximum height of the aux display area.
Definition: Editor.cc:473
void beginningOfLine(LineEditor &editor)
Move to beginning of line.
Definition: Editor.cc:600
Single line interactive text editor.
Definition: Editor.hh:202
virtual void setCallbacks(Callbacks &cb)=0
Register terminal callbacks.
#define prefix_
Definition: Editor.cc:25
unsigned point()
Get current cursor position.
Definition: Editor.cc:489
void set(std::string const &text, unsigned pos=0u)
Set edit buffer contents.
Definition: Editor.cc:304
void newline()
Move to beginning of a new, empty line.
Definition: Editor.cc:38
void clearScreen(LineEditor &editor)
Clear screen and redisplay editor.
Definition: Editor.cc:632
void maybeClrScr()
Clear screen if possible.
Definition: Editor.cc:119
std::string formatString(properties::String p, number_t arg1=NoValue, number_t arg2=NoValue, number_t arg3=NoValue, number_t arg4=NoValue, number_t arg5=NoValue, number_t arg6=NoValue, number_t arg7=NoValue, number_t arg8=NoValue, number_t arg9=NoValue) const
Format string property value.
Definition: Terminfo.cc:235
void clearAuxDisplay()
Clear the aux display area.
Definition: Editor.cc:478
void reset()
Reset display area to single line.
Definition: Editor.cc:168
KeyParser::keycode_t keycode_t
Definition: Editor.hh:55
void deleteChar(LineEditor &editor)
Delete char at cursor.
Definition: Editor.cc:595
void setBold()
Set bold char display.
Definition: Editor.cc:105
virtual unsigned width() const =0
Get current terminal window width.
void toColumn(unsigned c)
Move cursor to column c.
Definition: Editor.cc:46
void load(Terminfo const &ti)
Load keymap information from ti.
Definition: Terminfo.cc:509
unsigned width() const
Return current screen width.
Definition: Editor.cc:246
Abstract terminal interface.
keycode_t lastKey()
Get last key code received.
Definition: Editor.cc:499
void endOfLine(LineEditor &editor)
Move to end of line.
Definition: Editor.cc:605