Utils/Termlib: Fix handling of very narrow windows
[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_ unsigned senf::term::BaseEditor::currentColumn()
128    const
129 {
130     return column_;
131 }
132
133 prefix_ bool senf::term::BaseEditor::cb_init()
134 {
135     try {
136         tifo_.load(terminal_->terminalType());
137         keyParser_.load(tifo_);
138     }
139     catch (Terminfo::InvalidTerminfoException & ex) {
140         return false;
141     }
142
143     typedef Terminfo::properties p;
144     if (! (tifo_.hasProperty(p::ClrEol) &&
145            (tifo_.hasProperty(p::ParmRightCursor) || tifo_.hasProperty(p::CursorRight)) &&
146            (tifo_.hasProperty(p::ParmLeftCursor) || tifo_.hasProperty(p::CursorLeft))))
147         return false;
148
149     if (tifo_.hasProperty(Terminfo::properties::KeypadXmit))
150         write(tifo_.getString(Terminfo::properties::KeypadXmit));
151 }
152
153 prefix_ void senf::term::BaseEditor::cb_charReceived(char c)
154 {
155     inputBuffer_ += c;
156     timer_.timeout(senf::scheduler::eventTime() + keyTimeout_);
157     processKeys();
158 }
159
160 prefix_ void senf::term::BaseEditor::cb_windowSizeChanged()
161 {
162     if (column_ >= width())
163         column_ = width()-1;
164 }
165
166 prefix_ void senf::term::BaseEditor::keySequenceTimeout()
167 {
168     while (!inputBuffer_.empty()) {
169         processKeys();
170         v_keyReceived(keycode_t(inputBuffer_[0]));
171         inputBuffer_.erase(0, 1);
172     }
173 }
174
175 prefix_ void senf::term::BaseEditor::processKeys()
176 {
177     do {
178         std::pair<senf::term::KeyParser::keycode_t, std::string::size_type> result
179             (keyParser_.lookup(inputBuffer_));
180         if (result.first == senf::term::KeyParser::Incomplete)
181             return;
182         v_keyReceived(result.first);
183         inputBuffer_.erase(0, result.second);
184     } while (! inputBuffer_.empty());
185     timer_.disable();
186 }
187
188 prefix_ unsigned senf::term::BaseEditor::width()
189 {
190     return terminal_->width();
191 }
192
193 prefix_ void senf::term::BaseEditor::write(char ch)
194 {
195     terminal_->write(ch);
196 }
197
198 prefix_ void senf::term::BaseEditor::write(std::string const & s)
199 {
200     for (std::string::const_iterator i (s.begin()); i != s.end(); ++i)
201         write(*i);
202 }
203
204 ///////////////////////////////////////////////////////////////////////////
205
206 prefix_ senf::term::LineEditor::LineEditor(AbstractTerminal & terminal, AcceptCallback cb)
207     : BaseEditor(terminal), enabled_ (true), prompt_ ("$"), promptWidth_ (1u), editWidth_ (0u), 
208       text_ (""), point_ (0u), displayPos_ (0u), lastKey_ (0u), callback_ (cb)
209 {
210     defineKey(KeyParser::Return,    &bindings::accept);
211     defineKey(KeyParser::Right,     &bindings::forwardChar);
212     defineKey(KeyParser::Left,      &bindings::backwardChar);
213     defineKey(KeyParser::Backspace, &bindings::backwardDeleteChar);
214     defineKey(KeyParser::Delete,    &bindings::deleteChar);
215     defineKey(KeyParser::Home,      &bindings::beginningOfLine);
216     defineKey(KeyParser::End,       &bindings::endOfLine);
217     defineKey(KeyParser::Ctrl('K'), &bindings::deleteToEndOfLine);
218     defineKey(KeyParser::Ctrl('A'), &bindings::beginningOfLine);
219     defineKey(KeyParser::Ctrl('E'), &bindings::endOfLine);
220     defineKey(KeyParser::Ctrl('D'), &bindings::deleteChar);
221     defineKey(KeyParser::Ctrl('C'), &bindings::restartEdit);
222 }
223
224 prefix_ void senf::term::LineEditor::prompt(std::string const & text)
225 {
226     prompt_ = text;
227     promptWidth_ = prompt_.size();
228     if (promptWidth_ > width() - 4 && width() > 4)
229         promptWidth_ = width() - 4;
230     editWidth_ = width() - promptWidth_ - 3;
231     if (enabled_)
232         redisplay();
233 }
234
235 prefix_ void senf::term::LineEditor::set(std::string const & text, unsigned pos)
236 {
237     text_ = text;
238     point_ = pos;
239     if (point_ > text.size())
240         point_ = text.size();
241     displayPos_ = 0u;
242     if (point_ > editWidth_)
243         displayPos_ = point_ - editWidth_;
244     redisplay();
245 }
246
247 prefix_ void senf::term::LineEditor::show()
248 {
249     if (enabled_)
250         return;
251     enabled_ = true;
252     redisplay();
253 }
254
255 prefix_ void senf::term::LineEditor::hide()
256 {
257     if (! enabled_)
258         return;
259     clearLine();
260     enabled_ = false;
261 }
262
263 prefix_ void senf::term::LineEditor::accept()
264 {
265     if (enabled_)
266         newline();
267     hide();
268     callback_(text_);
269     clear();
270 }
271
272 prefix_ void senf::term::LineEditor::clear()
273 {
274     set("");
275 }
276
277 prefix_ void senf::term::LineEditor::redisplay()
278 {
279     redisplayNeeded_ = true;
280 }
281
282 prefix_ void senf::term::LineEditor::forceRedisplay()
283 {
284     if (! enabled_)
285         return;
286     clearLine();
287     setBold();
288     if (prompt_.size() > promptWidth_)
289         put(prompt_.substr(prompt_.size()-promptWidth_));
290     else
291         put(prompt_);
292     put( displayPos_ > 0 ? '<' : ' ' );
293     if (text_.size() > displayPos_ + editWidth_) {
294         toColumn(editWidth_ + promptWidth_ + 1);
295         put('>');
296         toColumn(promptWidth_ + 1);
297     }
298     setNormal();
299     put(text_.substr(displayPos_, editWidth_));
300     toColumn(point_ - displayPos_ + promptWidth_ + 1);
301     redisplayNeeded_ = false;
302 }
303
304 prefix_ void senf::term::LineEditor::gotoChar(unsigned n)
305 {
306     point_ = n;
307     if (point_ > text_.size())
308         point_ = text_.size();
309     if (point_ < displayPos_)
310         displayPos_ = point_;
311     if (point_ > displayPos_+editWidth_)
312         displayPos_ = point_-editWidth_;
313     redisplay();
314 }
315
316 prefix_ void senf::term::LineEditor::scrollTo(unsigned n)
317 {
318     displayPos_ = n;
319     if (displayPos_ > text_.size())
320         displayPos_ = text_.size();
321     if (point_ < displayPos_)
322         point_ = displayPos_;
323     if (point_ > displayPos_+editWidth_)
324         point_ = displayPos_+editWidth_;
325     redisplay();
326 }
327
328 prefix_ void senf::term::LineEditor::deleteChar(unsigned n)
329 {
330     if (point_ >= text_.size())
331         return;
332     text_.erase(point_, n);
333     redisplay();
334 }
335
336 prefix_ void senf::term::LineEditor::insert(char ch)
337 {
338     text_.insert(point_, std::string(1, ch));
339     gotoChar(point_+1);
340     redisplay();
341 }
342
343 prefix_ void senf::term::LineEditor::insert(std::string const & text)
344 {
345     text_.insert(point_, text);
346     gotoChar(point_+text.size());
347     redisplay();
348 }
349
350 prefix_ std::string const & senf::term::LineEditor::text()
351 {
352     return text_;
353 }
354
355 prefix_ unsigned senf::term::LineEditor::point()
356 {
357     return point_;
358 }
359
360 prefix_ unsigned senf::term::LineEditor::displayPos()
361 {
362     return displayPos_;
363 }
364
365 prefix_ senf::term::LineEditor::keycode_t senf::term::LineEditor::lastKey()
366 {
367     return lastKey_;
368 }
369
370 prefix_ void senf::term::LineEditor::defineKey(keycode_t key, KeyBinding binding)
371 {
372     bindings_[key] = binding;
373 }
374
375 prefix_ void senf::term::LineEditor::unsetKey(keycode_t key)
376 {
377     bindings_.erase(key);
378 }
379
380 prefix_ bool senf::term::LineEditor::cb_init()
381 {
382     if (!BaseEditor::cb_init())
383         return false;
384     prompt(prompt_);
385     forceRedisplay();
386     return true;
387 }
388
389 prefix_ void senf::term::LineEditor::cb_windowSizeChanged()
390 {
391     BaseEditor::cb_windowSizeChanged();
392     prompt(prompt_);
393     gotoChar(point_);
394     forceRedisplay();
395 }
396
397 prefix_ void senf::term::LineEditor::v_keyReceived(keycode_t key)
398 {
399     if (! enabled_)
400         return;
401     lastKey_ = key;
402     KeyMap::iterator i (bindings_.find(key));
403     if (i != bindings_.end())
404         i->second(*this);
405     else if (key >= ' ' && key < 256)
406         insert(char(key));
407     if (redisplayNeeded_)
408         forceRedisplay();
409 }
410
411 ///////////////////////////////////////////////////////////////////////////
412
413 prefix_ void senf::term::bindings::selfInsertCommand(LineEditor & editor)
414 {
415     LineEditor::keycode_t key (editor.lastKey());
416     if (key >= ' ' && key < 256)
417         editor.insert(key);
418 }
419
420 prefix_ void senf::term::bindings::forwardChar(LineEditor & editor)
421 {
422     editor.gotoChar(editor.point()+1);
423 }
424
425 prefix_ void senf::term::bindings::backwardChar(LineEditor & editor)
426 {
427     unsigned p (editor.point());
428     if (p>0)
429         editor.gotoChar(p-1);
430 }
431
432 prefix_ void senf::term::bindings::accept(LineEditor & editor)
433 {
434     editor.accept();
435 }
436
437 prefix_ void senf::term::bindings::backwardDeleteChar(LineEditor & editor)
438 {
439     unsigned p (editor.point());
440     if (p>0) {
441         editor.gotoChar(p-1);
442         editor.deleteChar();
443     }
444 }
445
446 prefix_ void senf::term::bindings::deleteChar(LineEditor & editor)
447 {
448     editor.deleteChar();
449 }
450
451 prefix_ void senf::term::bindings::beginningOfLine(LineEditor & editor)
452 {
453     editor.gotoChar(0u);
454 }
455
456 prefix_ void senf::term::bindings::endOfLine(LineEditor & editor)
457 {
458     editor.gotoChar(editor.text().size());
459 }
460
461 prefix_ void senf::term::bindings::deleteToEndOfLine(LineEditor & editor)
462 {
463     editor.deleteChar(editor.text().size()-editor.point());
464 }
465
466 prefix_ void senf::term::bindings::restartEdit(LineEditor & editor)
467 {
468     editor.newline();
469     editor.clear();
470     editor.redisplay();
471 }
472
473 ///////////////////////////////cc.e////////////////////////////////////////
474 #undef prefix_
475 //#include "Editor.mpp"
476
477 \f
478 // Local Variables:
479 // mode: c++
480 // fill-column: 100
481 // comment-column: 40
482 // c-file-style: "senf"
483 // indent-tabs-mode: nil
484 // ispell-local-dictionary: "american"
485 // compile-command: "scons -u test"
486 // End: