e710160d84f71c8c93421e8c09683ac57a143917
[senf.git] / senf / Utils / Termlib / Editor.cc
1 // $Id$
2 //
3 // Copyright (C) 2009
4 // Fraunhofer Institute for Open Communication Systems (FOKUS)
5 // Competence Center NETwork research (NET), St. Augustin, GERMANY
6 //     Stefan Bund <g0dil@berlios.de>
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 2 of the License, or
11 // (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the
20 // Free Software Foundation, Inc.,
21 // 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22
23 /** \file
24     \brief Editor non-inline non-template implementation */
25
26 #include "Editor.hh"
27 //#include "Editor.ih"
28
29 // Custom includes
30 #include <senf/Utils/membind.hh>
31 #include <senf/Scheduler/Scheduler.hh>
32
33 //#include "Editor.mpp"
34 #define prefix_
35 //-/////////////////////////////////////////////////////////////////////////////////////////////////
36
37 prefix_ senf::term::BaseEditor::BaseEditor(AbstractTerminal & terminal)
38     : terminal_ (&terminal),
39       keyTimeout_ (senf::ClockService::milliseconds(DEFAULT_KEY_TIMEOUT_MS)),
40       timer_ ("senf::term::BaseEditor::keySequenceTimeout",
41               senf::membind(&BaseEditor::keySequenceTimeout, this)),
42       column_ (0u), displayHeight_ (1u), line_ (0u)
43 {
44     terminal_->setCallbacks(*this);
45 }
46
47 prefix_ void senf::term::BaseEditor::newline()
48 {
49     reset();
50     write("\n");
51     write(tifo_.getString(Terminfo::properties::ClrEol));
52     column_ = 0;
53 }
54
55 prefix_ void senf::term::BaseEditor::toColumn(unsigned c)
56 {
57     if (c >= width())
58         c = width();
59     if (c > column_) {
60         if (tifo_.hasProperty(Terminfo::properties::ParmRightCursor)) {
61             write(tifo_.formatString(Terminfo::properties::ParmRightCursor, c - column_));
62             column_ = c;
63         }
64         else {
65             char const * cuf1 (tifo_.getString(Terminfo::properties::CursorRight));
66             while (c > column_) {
67                 write(cuf1);
68                 ++column_;
69             }
70         }
71     }
72     else if (c < column_) {
73         if (tifo_.hasProperty(Terminfo::properties::ParmLeftCursor)) {
74             write(tifo_.formatString(Terminfo::properties::ParmLeftCursor, column_ - c));
75             column_ = c;
76         }
77         else {
78             char const * cub1 (tifo_.getString(Terminfo::properties::CursorLeft));
79             while (c < column_) {
80                 write(cub1);
81                 --column_;
82             }
83         }
84     }
85 }
86
87 prefix_ void senf::term::BaseEditor::put(char ch)
88 {
89     if (column_ >= width()-1)
90         return;
91     write(ch);
92     ++ column_;
93 }
94
95 prefix_ void senf::term::BaseEditor::put(std::string const & text)
96 {
97     if (text.size() > width()-column_-1) {
98         write(text.substr(0,width()-column_-1));
99         column_ = width() - 1;
100     }
101     else {
102         write(text);
103         column_ += text.size();
104     }
105 }
106
107 prefix_ void senf::term::BaseEditor::clearLine()
108 {
109     write("\r");
110     write(tifo_.getString(Terminfo::properties::ClrEol));
111     column_ = 0;
112 }
113
114 prefix_ void senf::term::BaseEditor::setBold()
115 {
116     if (tifo_.hasProperty(Terminfo::properties::EnterBoldMode) &&
117         tifo_.hasProperty(Terminfo::properties::ExitAttributeMode))
118         write(tifo_.getString(Terminfo::properties::EnterBoldMode));
119 }
120
121 prefix_ void senf::term::BaseEditor::setNormal()
122 {
123     if (tifo_.hasProperty(Terminfo::properties::EnterBoldMode) &&
124         tifo_.hasProperty(Terminfo::properties::ExitAttributeMode))
125         write(tifo_.getString(Terminfo::properties::ExitAttributeMode));
126 }
127
128 prefix_ void senf::term::BaseEditor::maybeClrScr()
129 {
130     if (tifo_.hasProperty(Terminfo::properties::ClearScreen))
131         write(tifo_.getString(Terminfo::properties::ClearScreen));
132 }
133
134 prefix_ void senf::term::BaseEditor::toLine(unsigned l)
135 {
136     if (l >= height())
137         l = height() - 1;
138     unsigned ll (l);
139     if (ll >= displayHeight_)
140         ll = displayHeight_-1;
141     if (ll > line_) {
142         if (tifo_.hasProperty(Terminfo::properties::ParmDownCursor)) {
143             write(tifo_.formatString(Terminfo::properties::ParmDownCursor, ll - line_));
144             line_ = ll;
145         }
146         else {
147             char const * cud1 (tifo_.getString(Terminfo::properties::CursorDown));
148             while (ll > line_) {
149                 write(cud1);
150                 ++line_;
151             }
152         }
153     }
154     else if (ll < line_) {
155         if (tifo_.hasProperty(Terminfo::properties::ParmUpCursor)) {
156             write(tifo_.formatString(Terminfo::properties::ParmUpCursor, line_ - ll));
157             line_ = ll;
158         }
159         else {
160             char const * cuu1 (tifo_.getString(Terminfo::properties::CursorUp));
161             while (ll < line_) {
162                 write(cuu1);
163                 --line_;
164             }
165         }
166     }
167     while (line_ < l) {
168         write("\n");
169         write(tifo_.getString(Terminfo::properties::ClrEol));
170         ++displayHeight_;
171         ++line_;
172     }
173     write('\r');
174     column_ = 0;
175 }
176
177 prefix_ void senf::term::BaseEditor::reset()
178 {
179     for (unsigned i (1); i < displayHeight_; ++i) {
180         toLine(i);
181         clearLine();
182     }
183     toLine(0);
184     displayHeight_ = 1;
185 }
186
187 prefix_ unsigned senf::term::BaseEditor::currentColumn()
188    const
189 {
190     return column_;
191 }
192
193 prefix_ unsigned senf::term::BaseEditor::currentLine()
194     const
195 {
196     return line_;
197 }
198
199 prefix_ bool senf::term::BaseEditor::cb_init()
200 {
201     try {
202         tifo_.load(terminal_->terminalType());
203         keyParser_.load(tifo_);
204     }
205     catch (Terminfo::InvalidTerminfoException & ex) {
206         return false;
207     }
208
209     typedef Terminfo::properties p;
210     if (! (tifo_.hasProperty(p::ClrEol) &&
211            (tifo_.hasProperty(p::ParmRightCursor) || tifo_.hasProperty(p::CursorRight)) &&
212            (tifo_.hasProperty(p::ParmLeftCursor) || tifo_.hasProperty(p::CursorLeft))))
213         return false;
214
215     if (tifo_.hasProperty(Terminfo::properties::KeypadXmit))
216         write(tifo_.getString(Terminfo::properties::KeypadXmit));
217     return true;
218 }
219
220 prefix_ void senf::term::BaseEditor::cb_charReceived(char c)
221 {
222     inputBuffer_ += c;
223     timer_.timeout(senf::scheduler::eventTime() + keyTimeout_);
224     processKeys();
225 }
226
227 prefix_ void senf::term::BaseEditor::cb_windowSizeChanged()
228 {
229     if (column_ >= width())
230         column_ = width()-1;
231 }
232
233 prefix_ void senf::term::BaseEditor::keySequenceTimeout()
234 {
235     while (!inputBuffer_.empty()) {
236         processKeys();
237         v_keyReceived(keycode_t(inputBuffer_[0]));
238         inputBuffer_.erase(0, 1);
239     }
240 }
241
242 prefix_ void senf::term::BaseEditor::processKeys()
243 {
244     do {
245         std::pair<senf::term::KeyParser::keycode_t, std::string::size_type> result
246             (keyParser_.lookup(inputBuffer_));
247         if (result.first == senf::term::KeyParser::Incomplete)
248             return;
249         v_keyReceived(result.first);
250         inputBuffer_.erase(0, result.second);
251     } while (! inputBuffer_.empty());
252     timer_.disable();
253 }
254
255 prefix_ unsigned senf::term::BaseEditor::width()
256     const
257 {
258     return terminal_->width();
259 }
260
261 prefix_ unsigned senf::term::BaseEditor::height()
262     const
263 {
264     return terminal_->height();
265 }
266
267 prefix_ void senf::term::BaseEditor::write(char ch)
268 {
269     terminal_->write(ch);
270 }
271
272 prefix_ void senf::term::BaseEditor::write(std::string const & s)
273 {
274     for (std::string::const_iterator i (s.begin()); i != s.end(); ++i)
275         write(*i);
276 }
277
278 //-/////////////////////////////////////////////////////////////////////////////////////////////////
279
280 prefix_ senf::term::LineEditor::LineEditor(AbstractTerminal & terminal, AcceptCallback cb)
281     : BaseEditor(terminal), enabled_ (false), prompt_ ("$"), promptWidth_ (1u), editWidth_ (0u),
282       text_ (""), point_ (0u), displayPos_ (0u), lastKey_ (0u), callback_ (cb), historyPoint_ (0u)
283 {
284     defineKey(KeyParser::Return,    &bindings::accept);
285     defineKey(KeyParser::Right,     &bindings::forwardChar);
286     defineKey(KeyParser::Left,      &bindings::backwardChar);
287     defineKey(KeyParser::Up,        &bindings::prevHistory);
288     defineKey(KeyParser::Down,      &bindings::nextHistory);
289     defineKey(KeyParser::Backspace, &bindings::backwardDeleteChar);
290     defineKey(KeyParser::Delete,    &bindings::deleteChar);
291     defineKey(KeyParser::Home,      &bindings::beginningOfLine);
292     defineKey(KeyParser::End,       &bindings::endOfLine);
293     defineKey(KeyParser::Ctrl('K'), &bindings::deleteToEndOfLine);
294     defineKey(KeyParser::Ctrl('A'), &bindings::beginningOfLine);
295     defineKey(KeyParser::Ctrl('E'), &bindings::endOfLine);
296     defineKey(KeyParser::Ctrl('D'), &bindings::deleteChar);
297     defineKey(KeyParser::Ctrl('C'), &bindings::restartEdit);
298     defineKey(KeyParser::Ctrl('L'), &bindings::clearScreen);
299 }
300
301 prefix_ void senf::term::LineEditor::prompt(std::string const & text)
302 {
303     prompt_ = text;
304     promptWidth_ = prompt_.size();
305     if (promptWidth_ > width() - 4 && width() > 4)
306         promptWidth_ = width() - 4;
307     editWidth_ = width() - promptWidth_ - 3;
308     if (enabled_)
309         redisplay();
310 }
311
312 prefix_ void senf::term::LineEditor::set(std::string const & text, unsigned pos)
313 {
314     text_ = text;
315     point_ = pos;
316     if (point_ > text.size())
317         point_ = text.size();
318     displayPos_ = 0u;
319     if (point_ > editWidth_)
320         displayPos_ = point_ - editWidth_;
321     redisplay();
322 }
323
324 prefix_ void senf::term::LineEditor::show()
325 {
326     if (enabled_)
327         return;
328     enabled_ = true;
329     for (unsigned n (0); n < auxDisplay_.size(); ++n) {
330         toLine(n+1);
331         put(auxDisplay_[n]);
332     }
333     toLine(0);
334     forceRedisplay();
335 }
336
337 prefix_ void senf::term::LineEditor::hide()
338 {
339     if (! enabled_)
340         return;
341     reset();
342     clearLine();
343     enabled_ = false;
344 }
345
346 prefix_ void senf::term::LineEditor::accept()
347 {
348     if (enabled_)
349         newline();
350     hide();
351     pushHistory(text_, true);
352     callback_(text_);
353     clear();
354 }
355
356 prefix_ void senf::term::LineEditor::clear()
357 {
358     set("");
359     historyPoint_ = history_.size();
360 }
361
362 prefix_ void senf::term::LineEditor::redisplay()
363 {
364     redisplayNeeded_ = true;
365 }
366
367 prefix_ void senf::term::LineEditor::forceRedisplay()
368 {
369     if (! enabled_)
370         return;
371     clearLine();
372     setBold();
373     if (prompt_.size() > promptWidth_)
374         put(prompt_.substr(prompt_.size()-promptWidth_));
375     else
376         put(prompt_);
377     put( displayPos_ > 0 ? '<' : ' ' );
378     if (text_.size() > displayPos_ + editWidth_) {
379         toColumn(editWidth_ + promptWidth_ + 1);
380         put('>');
381         toColumn(promptWidth_ + 1);
382     }
383     setNormal();
384     put(text_.substr(displayPos_, editWidth_));
385     toColumn(point_ - displayPos_ + promptWidth_ + 1);
386     redisplayNeeded_ = false;
387 }
388
389 prefix_ void senf::term::LineEditor::gotoChar(unsigned n)
390 {
391     point_ = n;
392     if (point_ > text_.size())
393         point_ = text_.size();
394     if (point_ < displayPos_)
395         displayPos_ = point_;
396     if (point_ > displayPos_+editWidth_)
397         displayPos_ = point_-editWidth_;
398     redisplay();
399 }
400
401 prefix_ void senf::term::LineEditor::scrollTo(unsigned n)
402 {
403     displayPos_ = n;
404     if (displayPos_ > text_.size())
405         displayPos_ = text_.size();
406     if (point_ < displayPos_)
407         point_ = displayPos_;
408     if (point_ > displayPos_+editWidth_)
409         point_ = displayPos_+editWidth_;
410     redisplay();
411 }
412
413 prefix_ void senf::term::LineEditor::deleteChar(unsigned n)
414 {
415     if (point_ >= text_.size())
416         return;
417     text_.erase(point_, n);
418     redisplay();
419 }
420
421 prefix_ void senf::term::LineEditor::insert(char ch)
422 {
423     text_.insert(point_, std::string(1, ch));
424     gotoChar(point_+1);
425     redisplay();
426 }
427
428 prefix_ void senf::term::LineEditor::insert(std::string const & text)
429 {
430     text_.insert(point_, text);
431     gotoChar(point_+text.size());
432     redisplay();
433 }
434
435 prefix_ void senf::term::LineEditor::pushHistory(std::string const & text, bool accept)
436 {
437     if (! text.empty()
438         && (accept || historyPoint_ == history_.size() || history_[historyPoint_] != text)
439         && (history_.empty() || history_.back() != text)) {
440         history_.push_back(text);
441         while (history_.size() > MAX_HISTORY_SIZE)
442             history_.erase(history_.begin());
443         if (accept)
444             historyPoint_ = history_.size() - 1;
445     }
446 }
447
448 prefix_ void senf::term::LineEditor::prevHistory()
449 {
450     if (historyPoint_ <= 0)
451         return;
452     pushHistory(text_);
453     std::string entry (history_[--historyPoint_]);
454     set(entry, entry.size());
455 }
456
457 prefix_ void senf::term::LineEditor::nextHistory()
458 {
459     if (historyPoint_ >= history_.size())
460         return;
461     pushHistory(text_);
462     ++ historyPoint_;
463     if (historyPoint_ >= history_.size())
464         set("");
465     else {
466         std::string entry (history_[historyPoint_]);
467         set(entry, entry.size());
468     }
469 }
470
471 prefix_ void senf::term::LineEditor::auxDisplay(unsigned line, std::string const & text)
472 {
473     toLine(line+1);
474     clearLine();
475     put(text);
476     while (auxDisplay_.size() < line+1)
477         auxDisplay_.push_back("");
478     auxDisplay_[line] = text;
479 }
480
481 prefix_ unsigned senf::term::LineEditor::maxAuxDisplayHeight()
482 {
483     return height()-1;
484 }
485
486 prefix_ void senf::term::LineEditor::clearAuxDisplay()
487 {
488     reset();
489     auxDisplay_.clear();
490 }
491
492 prefix_ std::string const & senf::term::LineEditor::text()
493 {
494     return text_;
495 }
496
497 prefix_ unsigned senf::term::LineEditor::point()
498 {
499     return point_;
500 }
501
502 prefix_ unsigned senf::term::LineEditor::displayPos()
503 {
504     return displayPos_;
505 }
506
507 prefix_ senf::term::LineEditor::keycode_t senf::term::LineEditor::lastKey()
508 {
509     return lastKey_;
510 }
511
512 prefix_ void senf::term::LineEditor::defineKey(keycode_t key, KeyBinding binding)
513 {
514     bindings_[key] = binding;
515 }
516
517 prefix_ void senf::term::LineEditor::unsetKey(keycode_t key)
518 {
519     bindings_.erase(key);
520 }
521
522 prefix_ bool senf::term::LineEditor::cb_init()
523 {
524     if (!BaseEditor::cb_init())
525         return false;
526     prompt(prompt_);
527     show();
528     return true;
529 }
530
531 prefix_ void senf::term::LineEditor::cb_windowSizeChanged()
532 {
533     BaseEditor::cb_windowSizeChanged();
534     clearAuxDisplay();
535     prompt(prompt_);
536     gotoChar(point_);
537     forceRedisplay();
538 }
539
540 prefix_ void senf::term::LineEditor::v_keyReceived(keycode_t key)
541 {
542     if (! enabled_)
543         return;
544     clearAuxDisplay();
545     lastKey_ = key;
546     KeyMap::iterator i (bindings_.find(key));
547     if (i != bindings_.end())
548         i->second(*this);
549     else if (key >= ' ' && key < 256)
550         insert(char(key));
551     if (currentLine() != 0)
552         toLine(0);
553     if (redisplayNeeded_)
554         forceRedisplay();
555     else
556         toColumn(point_ - displayPos_ + promptWidth_ + 1);
557 }
558
559 //-/////////////////////////////////////////////////////////////////////////////////////////////////
560
561 prefix_ void senf::term::bindings::selfInsertCommand(LineEditor & editor)
562 {
563     LineEditor::keycode_t key (editor.lastKey());
564     if (key >= ' ' && key < 256)
565         editor.insert(key);
566 }
567
568 prefix_ void senf::term::bindings::forwardChar(LineEditor & editor)
569 {
570     editor.gotoChar(editor.point()+1);
571 }
572
573 prefix_ void senf::term::bindings::backwardChar(LineEditor & editor)
574 {
575     unsigned p (editor.point());
576     if (p>0)
577         editor.gotoChar(p-1);
578 }
579
580 prefix_ void senf::term::bindings::accept(LineEditor & editor)
581 {
582     editor.accept();
583 }
584
585 prefix_ void senf::term::bindings::acceptWithRepeat(LineEditor & editor)
586 {
587     if (editor.text().empty()) {
588         editor.prevHistory();
589         editor.forceRedisplay();
590     }
591     editor.accept();
592 }
593
594 prefix_ void senf::term::bindings::backwardDeleteChar(LineEditor & editor)
595 {
596     unsigned p (editor.point());
597     if (p>0) {
598         editor.gotoChar(p-1);
599         editor.deleteChar();
600     }
601 }
602
603 prefix_ void senf::term::bindings::deleteChar(LineEditor & editor)
604 {
605     editor.deleteChar();
606 }
607
608 prefix_ void senf::term::bindings::beginningOfLine(LineEditor & editor)
609 {
610     editor.gotoChar(0u);
611 }
612
613 prefix_ void senf::term::bindings::endOfLine(LineEditor & editor)
614 {
615     editor.gotoChar(editor.text().size());
616 }
617
618 prefix_ void senf::term::bindings::deleteToEndOfLine(LineEditor & editor)
619 {
620     editor.deleteChar(editor.text().size()-editor.point());
621 }
622
623 prefix_ void senf::term::bindings::restartEdit(LineEditor & editor)
624 {
625     editor.newline();
626     editor.clear();
627     editor.redisplay();
628 }
629
630 prefix_ void senf::term::bindings::prevHistory(LineEditor & editor)
631 {
632     editor.prevHistory();
633 }
634
635 prefix_ void senf::term::bindings::nextHistory(LineEditor & editor)
636 {
637     editor.nextHistory();
638 }
639
640 prefix_ void senf::term::bindings::clearScreen(LineEditor & editor)
641 {
642     editor.maybeClrScr();
643     editor.clearLine();
644     editor.forceRedisplay();
645 }
646
647 prefix_ void senf::term::bindings::complete(LineEditor & editor, Completer completer)
648 {
649     typedef std::vector<std::string> Completions;
650
651     std::string text (editor.text());
652     Completions completions;
653     unsigned b (0);
654     unsigned e (editor.point());
655     std::string prefix;
656     completer(editor, b, e, prefix, completions);
657     if (completions.empty())
658         return;
659     if (e > text.size())
660         e = text.size();
661     if (b > e)
662         b = e;
663
664     // Find common start string of all completions
665     unsigned commonStart (completions[0].size());
666     unsigned maxLen (commonStart);
667     for (Completions::const_iterator i (boost::next(completions.begin()));
668          i != completions.end(); ++i) {
669         if (i->size() > maxLen)
670             maxLen = i->size();
671         unsigned n (0u);
672         for (; n < commonStart && n < i->size() && completions[0][n] == (*i)[n]; ++n) ;
673         commonStart = n;
674     }
675
676     // Replace to-be-completed string with the common start string shared by all completions
677     std::string completion (prefix+completions[0].substr(0, commonStart));
678     bool didComplete (false);
679     if (text.substr(b, e) != completion) {
680         text.erase(b, e);
681         text.insert(b, completion);
682         didComplete = true;
683     }
684
685     // Otherwise place cursor directly after the (possibly partial) completion
686     editor.set(text, b+prefix.size()+commonStart);
687     if (didComplete || completions.size() == 1)
688         return;
689
690     // Text was not changed, show list of possible completions
691     unsigned colWidth (maxLen+2);
692     unsigned nColumns ((editor.width()-1) / colWidth);
693     if (nColumns < 1) nColumns = 1;
694     unsigned nRows ((completions.size()+nColumns-1) / nColumns);
695     if (nRows > editor.maxAuxDisplayHeight()) {
696         editor.auxDisplay(0, "(too many completions)");
697         return;
698     }
699     Completions::iterator i (completions.begin());
700     for (unsigned row (0); row < nRows; ++row) {
701         std::string line;
702         for (unsigned column (0); column < nColumns && i != completions.end(); ++column, ++i) {
703             std::string entry (colWidth, ' ');
704             std::copy(i->begin(),
705                       i->size() > colWidth-2 ? i->begin()+colWidth-2 : i->end(),
706                       entry.begin());
707             line += entry;
708         }
709         editor.auxDisplay(row, line);
710     }
711 }
712
713 //-/////////////////////////////////////////////////////////////////////////////////////////////////
714 #undef prefix_
715 //#include "Editor.mpp"
716
717 \f
718 // Local Variables:
719 // mode: c++
720 // fill-column: 100
721 // comment-column: 40
722 // c-file-style: "senf"
723 // indent-tabs-mode: nil
724 // ispell-local-dictionary: "american"
725 // compile-command: "scons -u test"
726 // End: