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