d8bd15e9aa7516e6d9f0e30d31c44a12401965c3
[senf.git] / 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 ///////////////////////////////cc.p////////////////////////////////////////
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)
43 {
44     terminal_->setCallbacks(*this);
45 }
46
47 prefix_ void senf::term::BaseEditor::newline()
48 {
49     write("\r\n");
50     write(tifo_.getString(Terminfo::properties::ClrEol));
51     column_ = 0;
52 }
53
54 prefix_ void senf::term::BaseEditor::toColumn(unsigned c)
55 {
56     if (c >= width())
57         c = width();
58     if (c > column_) {
59         if (tifo_.hasProperty(Terminfo::properties::ParmRightCursor)) {
60             write(tifo_.formatString(Terminfo::properties::ParmRightCursor, c - column_));
61             column_ = c;
62         }
63         else {
64             char const * cuf1 (tifo_.getString(Terminfo::properties::CursorRight));
65             while (c > column_) {
66                 write(cuf1);
67                 ++column_;
68             }
69         }
70     }
71     else if (c < column_) {
72         if (tifo_.hasProperty(Terminfo::properties::ParmLeftCursor)) {
73             write(tifo_.formatString(Terminfo::properties::ParmLeftCursor, column_ - c));
74             column_ = c;
75         }
76         else {
77             char const * cub1 (tifo_.getString(Terminfo::properties::CursorLeft));
78             while (c < column_) {
79                 write(cub1);
80                 --column_;
81             }
82         }
83     }
84 }
85
86 prefix_ void senf::term::BaseEditor::put(char ch)
87 {
88     if (column_ >= width()-1)
89         return;
90     write(ch);
91     ++ column_;
92 }
93
94 prefix_ void senf::term::BaseEditor::put(std::string const & text)
95 {
96     if (text.size() > width()-column_-1) {
97         write(text.substr(0,width()-column_-1));
98         column_ = width() - 1;
99     }
100     else {
101         write(text);
102         column_ += text.size();
103     }
104 }
105
106 prefix_ void senf::term::BaseEditor::clearLine()
107 {
108     write("\r");
109     write(tifo_.getString(Terminfo::properties::ClrEol));
110     column_ = 0;
111 }
112
113 prefix_ void senf::term::BaseEditor::setBold()
114 {
115     if (tifo_.hasProperty(Terminfo::properties::EnterBoldMode) &&
116         tifo_.hasProperty(Terminfo::properties::ExitAttributeMode))
117         write(tifo_.getString(Terminfo::properties::EnterBoldMode));
118 }
119
120 prefix_ void senf::term::BaseEditor::setNormal()
121 {
122     if (tifo_.hasProperty(Terminfo::properties::EnterBoldMode) &&
123         tifo_.hasProperty(Terminfo::properties::ExitAttributeMode))
124         write(tifo_.getString(Terminfo::properties::ExitAttributeMode));
125 }
126
127 prefix_ void senf::term::BaseEditor::maybeClrScr()
128 {
129     if (tifo_.hasProperty(Terminfo::properties::ClearScreen))
130         write(tifo_.getString(Terminfo::properties::ClearScreen));
131 }
132
133 prefix_ unsigned senf::term::BaseEditor::currentColumn()
134    const
135 {
136     return column_;
137 }
138
139 prefix_ bool senf::term::BaseEditor::cb_init()
140 {
141     try {
142         tifo_.load(terminal_->terminalType());
143         keyParser_.load(tifo_);
144     }
145     catch (Terminfo::InvalidTerminfoException & ex) {
146         return false;
147     }
148
149     typedef Terminfo::properties p;
150     if (! (tifo_.hasProperty(p::ClrEol) &&
151            (tifo_.hasProperty(p::ParmRightCursor) || tifo_.hasProperty(p::CursorRight)) &&
152            (tifo_.hasProperty(p::ParmLeftCursor) || tifo_.hasProperty(p::CursorLeft))))
153         return false;
154
155     if (tifo_.hasProperty(Terminfo::properties::KeypadXmit))
156         write(tifo_.getString(Terminfo::properties::KeypadXmit));
157     return true;
158 }
159
160 prefix_ void senf::term::BaseEditor::cb_charReceived(char c)
161 {
162     inputBuffer_ += c;
163     timer_.timeout(senf::scheduler::eventTime() + keyTimeout_);
164     processKeys();
165 }
166
167 prefix_ void senf::term::BaseEditor::cb_windowSizeChanged()
168 {
169     if (column_ >= width())
170         column_ = width()-1;
171 }
172
173 prefix_ void senf::term::BaseEditor::keySequenceTimeout()
174 {
175     while (!inputBuffer_.empty()) {
176         processKeys();
177         v_keyReceived(keycode_t(inputBuffer_[0]));
178         inputBuffer_.erase(0, 1);
179     }
180 }
181
182 prefix_ void senf::term::BaseEditor::processKeys()
183 {
184     do {
185         std::pair<senf::term::KeyParser::keycode_t, std::string::size_type> result
186             (keyParser_.lookup(inputBuffer_));
187         if (result.first == senf::term::KeyParser::Incomplete)
188             return;
189         v_keyReceived(result.first);
190         inputBuffer_.erase(0, result.second);
191     } while (! inputBuffer_.empty());
192     timer_.disable();
193 }
194
195 prefix_ unsigned senf::term::BaseEditor::width()
196 {
197     return terminal_->width();
198 }
199
200 prefix_ void senf::term::BaseEditor::write(char ch)
201 {
202     terminal_->write(ch);
203 }
204
205 prefix_ void senf::term::BaseEditor::write(std::string const & s)
206 {
207     for (std::string::const_iterator i (s.begin()); i != s.end(); ++i)
208         write(*i);
209 }
210
211 ///////////////////////////////////////////////////////////////////////////
212
213 prefix_ senf::term::LineEditor::LineEditor(AbstractTerminal & terminal, AcceptCallback cb)
214     : BaseEditor(terminal), enabled_ (true), prompt_ ("$"), promptWidth_ (1u), editWidth_ (0u), 
215       text_ (""), point_ (0u), displayPos_ (0u), lastKey_ (0u), callback_ (cb), historyPoint_ (0u)
216 {
217     defineKey(KeyParser::Return,    &bindings::accept);
218     defineKey(KeyParser::Right,     &bindings::forwardChar);
219     defineKey(KeyParser::Left,      &bindings::backwardChar);
220     defineKey(KeyParser::Up,        &bindings::prevHistory);
221     defineKey(KeyParser::Down,      &bindings::nextHistory);
222     defineKey(KeyParser::Backspace, &bindings::backwardDeleteChar);
223     defineKey(KeyParser::Delete,    &bindings::deleteChar);
224     defineKey(KeyParser::Home,      &bindings::beginningOfLine);
225     defineKey(KeyParser::End,       &bindings::endOfLine);
226     defineKey(KeyParser::Ctrl('K'), &bindings::deleteToEndOfLine);
227     defineKey(KeyParser::Ctrl('A'), &bindings::beginningOfLine);
228     defineKey(KeyParser::Ctrl('E'), &bindings::endOfLine);
229     defineKey(KeyParser::Ctrl('D'), &bindings::deleteChar);
230     defineKey(KeyParser::Ctrl('C'), &bindings::restartEdit);
231     defineKey(KeyParser::Ctrl('L'), &bindings::clearScreen);
232 }
233
234 prefix_ void senf::term::LineEditor::prompt(std::string const & text)
235 {
236     prompt_ = text;
237     promptWidth_ = prompt_.size();
238     if (promptWidth_ > width() - 4 && width() > 4)
239         promptWidth_ = width() - 4;
240     editWidth_ = width() - promptWidth_ - 3;
241     if (enabled_)
242         redisplay();
243 }
244
245 prefix_ void senf::term::LineEditor::set(std::string const & text, unsigned pos)
246 {
247     text_ = text;
248     point_ = pos;
249     if (point_ > text.size())
250         point_ = text.size();
251     displayPos_ = 0u;
252     if (point_ > editWidth_)
253         displayPos_ = point_ - editWidth_;
254     redisplay();
255 }
256
257 prefix_ void senf::term::LineEditor::show()
258 {
259     if (enabled_)
260         return;
261     enabled_ = true;
262     forceRedisplay();
263 }
264
265 prefix_ void senf::term::LineEditor::hide()
266 {
267     if (! enabled_)
268         return;
269     clearLine();
270     enabled_ = false;
271 }
272
273 prefix_ void senf::term::LineEditor::accept()
274 {
275     if (enabled_)
276         newline();
277     hide();
278     pushHistory(text_);
279     callback_(text_);
280     clear();
281 }
282
283 prefix_ void senf::term::LineEditor::clear()
284 {
285     set("");
286     historyPoint_ = history_.size();
287 }
288
289 prefix_ void senf::term::LineEditor::redisplay()
290 {
291     redisplayNeeded_ = true;
292 }
293
294 prefix_ void senf::term::LineEditor::forceRedisplay()
295 {
296     if (! enabled_)
297         return;
298     clearLine();
299     setBold();
300     if (prompt_.size() > promptWidth_)
301         put(prompt_.substr(prompt_.size()-promptWidth_));
302     else
303         put(prompt_);
304     put( displayPos_ > 0 ? '<' : ' ' );
305     if (text_.size() > displayPos_ + editWidth_) {
306         toColumn(editWidth_ + promptWidth_ + 1);
307         put('>');
308         toColumn(promptWidth_ + 1);
309     }
310     setNormal();
311     put(text_.substr(displayPos_, editWidth_));
312     toColumn(point_ - displayPos_ + promptWidth_ + 1);
313     redisplayNeeded_ = false;
314 }
315
316 prefix_ void senf::term::LineEditor::gotoChar(unsigned n)
317 {
318     point_ = n;
319     if (point_ > text_.size())
320         point_ = text_.size();
321     if (point_ < displayPos_)
322         displayPos_ = point_;
323     if (point_ > displayPos_+editWidth_)
324         displayPos_ = point_-editWidth_;
325     redisplay();
326 }
327
328 prefix_ void senf::term::LineEditor::scrollTo(unsigned n)
329 {
330     displayPos_ = n;
331     if (displayPos_ > text_.size())
332         displayPos_ = text_.size();
333     if (point_ < displayPos_)
334         point_ = displayPos_;
335     if (point_ > displayPos_+editWidth_)
336         point_ = displayPos_+editWidth_;
337     redisplay();
338 }
339
340 prefix_ void senf::term::LineEditor::deleteChar(unsigned n)
341 {
342     if (point_ >= text_.size())
343         return;
344     text_.erase(point_, n);
345     redisplay();
346 }
347
348 prefix_ void senf::term::LineEditor::insert(char ch)
349 {
350     text_.insert(point_, std::string(1, ch));
351     gotoChar(point_+1);
352     redisplay();
353 }
354
355 prefix_ void senf::term::LineEditor::insert(std::string const & text)
356 {
357     text_.insert(point_, text);
358     gotoChar(point_+text.size());
359     redisplay();
360 }
361
362 prefix_ void senf::term::LineEditor::pushHistory(std::string const & text)
363 {
364     if (! text.empty()
365         && (historyPoint_ == history_.size() || history_[historyPoint_] != text)
366         && (history_.empty() || history_.back() != text)) {
367         history_.push_back(text);
368         while (history_.size() > MAX_HISTORY_SIZE)
369             history_.erase(history_.begin());
370         historyPoint_ = history_.size() - 1;
371     }
372 }
373
374 prefix_ void senf::term::LineEditor::prevHistory()
375 {
376     if (historyPoint_ <= 0)
377         return;
378     pushHistory(text_);
379     std::string entry (history_[--historyPoint_]);
380     set(entry, entry.size());
381 }
382
383 prefix_ void senf::term::LineEditor::nextHistory()
384 {
385     if (historyPoint_ >= history_.size())
386         return;
387     pushHistory(text_);
388     ++ historyPoint_;
389     if (historyPoint_ >= history_.size())
390         set("");
391     else {
392         std::string entry (history_[historyPoint_]);
393         set(entry, entry.size());
394     }
395 }
396
397 prefix_ std::string const & senf::term::LineEditor::text()
398 {
399     return text_;
400 }
401
402 prefix_ unsigned senf::term::LineEditor::point()
403 {
404     return point_;
405 }
406
407 prefix_ unsigned senf::term::LineEditor::displayPos()
408 {
409     return displayPos_;
410 }
411
412 prefix_ senf::term::LineEditor::keycode_t senf::term::LineEditor::lastKey()
413 {
414     return lastKey_;
415 }
416
417 prefix_ void senf::term::LineEditor::defineKey(keycode_t key, KeyBinding binding)
418 {
419     bindings_[key] = binding;
420 }
421
422 prefix_ void senf::term::LineEditor::unsetKey(keycode_t key)
423 {
424     bindings_.erase(key);
425 }
426
427 prefix_ bool senf::term::LineEditor::cb_init()
428 {
429     if (!BaseEditor::cb_init())
430         return false;
431     prompt(prompt_);
432     forceRedisplay();
433     return true;
434 }
435
436 prefix_ void senf::term::LineEditor::cb_windowSizeChanged()
437 {
438     BaseEditor::cb_windowSizeChanged();
439     prompt(prompt_);
440     gotoChar(point_);
441     forceRedisplay();
442 }
443
444 prefix_ void senf::term::LineEditor::v_keyReceived(keycode_t key)
445 {
446     if (! enabled_)
447         return;
448     lastKey_ = key;
449     KeyMap::iterator i (bindings_.find(key));
450     if (i != bindings_.end())
451         i->second(*this);
452     else if (key >= ' ' && key < 256)
453         insert(char(key));
454     if (redisplayNeeded_)
455         forceRedisplay();
456 }
457
458 ///////////////////////////////////////////////////////////////////////////
459
460 prefix_ void senf::term::bindings::selfInsertCommand(LineEditor & editor)
461 {
462     LineEditor::keycode_t key (editor.lastKey());
463     if (key >= ' ' && key < 256)
464         editor.insert(key);
465 }
466
467 prefix_ void senf::term::bindings::forwardChar(LineEditor & editor)
468 {
469     editor.gotoChar(editor.point()+1);
470 }
471
472 prefix_ void senf::term::bindings::backwardChar(LineEditor & editor)
473 {
474     unsigned p (editor.point());
475     if (p>0)
476         editor.gotoChar(p-1);
477 }
478
479 prefix_ void senf::term::bindings::accept(LineEditor & editor)
480 {
481     editor.accept();
482 }
483
484 prefix_ void senf::term::bindings::backwardDeleteChar(LineEditor & editor)
485 {
486     unsigned p (editor.point());
487     if (p>0) {
488         editor.gotoChar(p-1);
489         editor.deleteChar();
490     }
491 }
492
493 prefix_ void senf::term::bindings::deleteChar(LineEditor & editor)
494 {
495     editor.deleteChar();
496 }
497
498 prefix_ void senf::term::bindings::beginningOfLine(LineEditor & editor)
499 {
500     editor.gotoChar(0u);
501 }
502
503 prefix_ void senf::term::bindings::endOfLine(LineEditor & editor)
504 {
505     editor.gotoChar(editor.text().size());
506 }
507
508 prefix_ void senf::term::bindings::deleteToEndOfLine(LineEditor & editor)
509 {
510     editor.deleteChar(editor.text().size()-editor.point());
511 }
512
513 prefix_ void senf::term::bindings::restartEdit(LineEditor & editor)
514 {
515     editor.newline();
516     editor.clear();
517     editor.redisplay();
518 }
519
520 prefix_ void senf::term::bindings::prevHistory(LineEditor & editor)
521 {
522     editor.prevHistory();
523 }
524
525 prefix_ void senf::term::bindings::nextHistory(LineEditor & editor)
526 {
527     editor.nextHistory();
528 }
529
530 prefix_ void senf::term::bindings::clearScreen(LineEditor & editor)
531 {
532     editor.maybeClrScr();
533     editor.clearLine();
534     editor.forceRedisplay();
535 }
536
537 ///////////////////////////////cc.e////////////////////////////////////////
538 #undef prefix_
539 //#include "Editor.mpp"
540
541 \f
542 // Local Variables:
543 // mode: c++
544 // fill-column: 100
545 // comment-column: 40
546 // c-file-style: "senf"
547 // indent-tabs-mode: nil
548 // ispell-local-dictionary: "american"
549 // compile-command: "scons -u test"
550 // End: