xoreos  0.0.5
readline.cpp
Go to the documentation of this file.
1 /* xoreos - A reimplementation of BioWare's Aurora engine
2  *
3  * xoreos is the legal property of its developers, whose names
4  * can be found in the AUTHORS file distributed with this source
5  * distribution.
6  *
7  * xoreos is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 3
10  * of the License, or (at your option) any later version.
11  *
12  * xoreos is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with xoreos. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
25 #include <algorithm>
26 
27 #include "src/common/util.h"
28 #include "src/common/readline.h"
29 
30 #include "src/events/events.h"
31 
32 namespace Common {
33 
34 ReadLine::ReadLine(size_t historySize) :
35  _historySizeMax(historySize), _historySizeCurrent(0),
36  _historyIgnoreSpace(false), _historyIgnoreDups(false), _historyEraseDups(false),
37  _cursorPosition(0), _overwrite(false), _maxHintSize(0) {
38 
39  _historyPosition = _history.end();
40 }
41 
43 }
44 
45 void ReadLine::historyIgnoreSpace(bool ignoreSpace) {
46  _historyIgnoreSpace = ignoreSpace;
47 }
48 
49 void ReadLine::historyIgnoreDups(bool ignoreDups) {
50  _historyIgnoreDups = ignoreDups;
51 }
52 
53 void ReadLine::historyEraseDups(bool eraseDups) {
54  _historyEraseDups = eraseDups;
55 }
56 
58  _history.clear();
60 }
61 
62 void ReadLine::addCommand(const UString &command) {
63  _commands.insert(command);
64 }
65 
66 void ReadLine::setArguments(const UString &command, const std::vector<UString> &arguments) {
67  std::pair<ArgumentSets::iterator, bool> result =
68  _arguments.insert(std::make_pair(command, CommandSet()));
69 
70  result.first->second.clear();
71  std::copy(arguments.begin(), arguments.end(),
72  std::inserter(result.first->second, result.first->second.end()));
73 }
74 
75 void ReadLine::setArguments(const UString &command) {
76  ArgumentSets::iterator args = _arguments.find(command);
77  if (args != _arguments.end())
78  _arguments.erase(args);
79 }
80 
82  return _currentLine;
83 }
84 
86  return _cursorPosition;
87 }
88 
89 bool ReadLine::getOverwrite() const {
90  return _overwrite;
91 }
92 
93 const std::vector<UString> &ReadLine::getCompleteHint(size_t &maxSize) const {
94  maxSize = _maxHintSize;
95 
96  return _completeHint;
97 }
98 
100  if (c == 0)
101  return;
102 
103  if (_overwrite)
105  else
107 
108  _cursorPosition++;
109 
110  updateHistory();
111 }
112 
113 void ReadLine::addInput(const UString &str) {
114  for (UString::iterator c = str.begin(); c != str.end(); ++c)
115  addInput(*c);
116 }
117 
118 bool ReadLine::processEvent(const Events::Event &event, UString &command) {
119  command.clear();
120 
121  _completeHint.clear();
122  _maxHintSize = 0;
123 
124  if (event.type == Events::kEventKeyDown)
125  return processKeyDown(event, command);
126  if (event.type == Events::kEventTextInput)
127  return processTextInput(event, command);
128 
129  return false;
130 }
131 
132 bool ReadLine::processKeyDown(const Events::Event &event, UString &command) {
133  // We only care about certain modifiers
134  SDL_Keycode key = event.key.keysym.sym;
135  SDL_Keymod mod = (SDL_Keymod) (((int) event.key.keysym.mod) & (KMOD_CTRL | KMOD_SHIFT | KMOD_ALT));
136 
137 
138  // Return / Enter: Execute this line
139  if ((key == SDLK_RETURN) || (key == SDLK_KP_ENTER)) {
140  command = _currentLine;
142  return true;
143  }
144 
145  // Backspace: Delete character left of the cursor
146  if ((key == SDLK_BACKSPACE) && (mod == KMOD_NONE)) {
147  if (!_currentLine.empty() && (_cursorPosition > 0)) {
148  _cursorPosition--;
150  }
151  updateHistory();
152  return true;
153  }
154 
155  // Delete / CTRL-D: Delete character right of the cursor
156  if ((key == SDLK_DELETE) || ((key == SDLK_d) && (mod & KMOD_CTRL))) {
159  updateHistory();
160  return true;
161  }
162 
163  // CTRL-U: Delete everything from the start of the line up to the cursor
164  if ((key == SDLK_u) && (mod & KMOD_CTRL)) {
166  _cursorPosition = 0;
167  updateHistory();
168  return true;
169  }
170 
171  // CTRL-K: Delete everything from the cursor to the end of the line
172  if ((key == SDLK_k) && (mod & KMOD_CTRL)) {
174  updateHistory();
175  return true;
176  }
177 
178  // Insert: Toggle insert/replace
179  if (key == SDLK_INSERT) {
181  return true;
182  }
183 
184  // CTRL-Left / ALT-B: Move to the start of the last word
185  if (((key == SDLK_LEFT) && (mod & KMOD_CTRL)) || ((key == SDLK_b) && (mod & KMOD_ALT))) {
187  return true;
188  }
189 
190  // CTRL-Right / ALT-F: Move to the end of the next word
191  if (((key == SDLK_RIGHT) && (mod & KMOD_CTRL)) || ((key == SDLK_f) && (mod & KMOD_ALT))) {
193  return true;
194  }
195 
196  // ALT-Backspace: Delete the last word
197  if ((key == SDLK_BACKSPACE) && (mod & KMOD_ALT)) {
198  size_t lastWordStart = findLastWordStart();
199 
201 
202  _cursorPosition = lastWordStart;
203  return true;
204  }
205 
206  // ALT-d: Delete the next word
207  if ((key == SDLK_d) && (mod & KMOD_ALT)) {
208  size_t nextWordEnd = findNextWordEnd();
209 
211  return true;
212  }
213 
214  // CTRL-w: Delete the last word (but only consider spaces to be word separators)
215  if ((key == SDLK_w) && (mod & KMOD_CTRL)) {
216  size_t lastWordStart = findLastWordStart(true);
217 
219 
220  _cursorPosition = lastWordStart;
221  return true;
222  }
223 
224  // Left / CTRL-B: Move one character to the left
225  if ((key == SDLK_LEFT) || ((key == SDLK_b) && (mod & KMOD_CTRL))) {
226  if (_cursorPosition > 0)
227  _cursorPosition--;
228  return true;
229  }
230 
231  // Right / CTRL-F: Move one character to the right
232  if ((key == SDLK_RIGHT) || ((key == SDLK_f) && (mod & KMOD_CTRL))) {
234  _cursorPosition++;
235  return true;
236  }
237 
238  // Home / CTRL-A: Move to the start of the line
239  if ((key == SDLK_HOME) || ((key == SDLK_a) && (mod & KMOD_CTRL))) {
240  _cursorPosition = 0;
241  return true;
242  }
243 
244  // End / CTRL-E: Move to the end of the line
245  if ((key == SDLK_END) || ((key == SDLK_e) && (mod & KMOD_CTRL))) {
247  return true;
248  }
249 
250  // Up / CTRL-p: Move up in the history
251  if ((key == SDLK_UP) || ((key == SDLK_p) && (mod & KMOD_CTRL))) {
252  browseUp();
253  return true;
254  }
255 
256  // Down / CTRL-n: Move down in the history
257  if ((key == SDLK_DOWN) || ((key == SDLK_n) && (mod & KMOD_CTRL))) {
258  browseDown();
259  return true;
260  }
261 
262  // ALT->: Move to the bottom of the history
263  if ((key == SDLK_LESS) && (mod & KMOD_ALT) && (mod & KMOD_SHIFT)) {
264  browseBottom();
265  return true;
266  }
267 
268  // ALT-<: Move to the top of the history
269  if ((key == SDLK_LESS) && (mod & KMOD_ALT)) {
270  browseTop();
271  return true;
272  }
273 
274  // TAB: Auto-complete
275  if (key == SDLK_TAB) {
276  tabComplete();
277  updateHistory();
278  return true;
279  }
280 
281  return false;
282 }
283 
285  UString text = EventMan.getTextInput(event);
286  if (text.empty())
287  return false;
288 
289  if (_overwrite)
291  else
293 
294  _cursorPosition += text.size();
295 
296  updateHistory();
297 
298  return true;
299 }
300 
303 }
304 
305 std::list<ReadLine::HistorySave>::iterator ReadLine::findHistorySave() {
306  for (std::list<HistorySave>::iterator save = _historySave.begin();
307  save != _historySave.end(); ++save)
308  if (save->position == _historyPosition)
309  return save;
310 
311  return _historySave.end();
312 }
313 
315  if (_historyPosition != _history.end()) {
316  // We modified a copied history line. Save it...
317  std::list<HistorySave>::iterator save = findHistorySave();
318  if (save == _historySave.end()) {
319  _historySave.push_back(HistorySave());
320 
321  _historySave.back().position = _historyPosition;
322  _historySave.back().line = *_historyPosition;
323  }
324 
325  // ...and copy the modified line into the history
327  }
328 }
329 
331  if (_currentLine.empty())
332  return;
333 
334  // We submitted a modified history line. Copy back the original.
335  std::list<HistorySave>::iterator save = findHistorySave();
336  if (save != _historySave.end())
337  *save->position = save->line;
338 
339  // Erase duplicate lines
340  if (_historyEraseDups) {
341  for (std::list<UString>::iterator h = _history.begin(); h != _history.end(); ) {
342  if (*h == _currentLine)
343  h = _history.erase(h);
344  else
345  ++h;
346  }
347  }
348 
349  // Ignore duplicate lines and/or lines starting with a space
350  bool shouldSave = true;
351  if (_historyIgnoreSpace && (*_currentLine.begin() == ' '))
352  shouldSave = false;
353  if (_historyIgnoreDups && !_history.empty() && (_history.back() == _currentLine))
354  shouldSave = false;
355 
356  // Add the line to the history
357  if (shouldSave) {
358  _history.push_back(_currentLine);
360  _history.pop_front();
361  else
363  }
364 
365 
366  // Clear the input
367 
370  _cursorPosition = 0;
371 
372  _overwrite = false;
373 
374  _historyPosition = _history.end();
375 
376  _historySave.clear();
377 }
378 
380  if (_history.empty())
381  // Empty history, can't browse
382  return;
383 
384  if (_historyPosition == _history.begin())
385  // Can't browse further up
386  return;
387 
388  _overwrite = false;
389 
390  // We're currently at the bottom, modified a potential new line. Save it.
391  if (_historyPosition == _history.end())
393 
395 
396  // Get a line out of the history
399 }
400 
402  if (_historyPosition == _history.end())
403  // Can't browse further down
404  return;
405 
406  _overwrite = false;
407 
409  if (_historyPosition == _history.end()) {
410  // We're at the bottom, restore the potential new line
413  return;
414  }
415 
416  // Get a line out of the history
419 }
420 
422  if (_history.empty())
423  // Empty history, can't browse
424  return;
425 
426  if (_historyPosition == _history.begin())
427  // Already at the top
428  return;
429 
430  _overwrite = false;
431 
432  // We're currently at the bottom, modified a potential new line. Save it.
433  if (_historyPosition == _history.end())
435 
436  _historyPosition = _history.begin();
437 
438  // Get a line out of the history
441 }
442 
444  if (_historyPosition == _history.end())
445  // Already at the bottom
446  return;
447 
448  _overwrite = false;
449 
450  _historyPosition = _history.end();
451 
452  // Restore the potential new line
455 }
456 
458  UString::iterator separator = _currentLine.findFirst(' ');
459  if (separator == _currentLine.end()) {
461  return;
462  }
463 
464  UString command, arguments;
465  _currentLine.split(separator, command, arguments);
466 
467  arguments.trimLeft();
468 
469  ArgumentSets::iterator args = _arguments.find(command);
470  if (args == _arguments.end())
471  return;
472 
473  tabComplete(command + " ", arguments, args->second);
474 }
475 
476 void ReadLine::tabComplete(const UString &prefix, const UString &input,
477  const CommandSet &commands) {
478 
479  // Find the first command that's greater than the current input
480  CommandSet::const_iterator lower = commands.lower_bound(input);
481  if (lower == commands.end())
482  return;
483 
484  size_t maxSize = 0;
485  size_t count = 0;
486 
487  // All commands starting with the current input are match candidates
488  std::list<UString> candidates;
489  for (CommandSet::const_iterator it = lower; it != commands.end(); ++it) {
490  if (!it->beginsWith(input))
491  break;
492 
493  if (!it->empty()) {
494  candidates.push_back(*it);
495  maxSize = MAX(maxSize, candidates.back().size());
496  count++;
497  }
498  }
499 
500  if (candidates.empty())
501  // No match
502  return;
503 
504  if (&candidates.front() == &candidates.back()) {
505  // Perfect match, complete
506 
507  _currentLine = prefix + candidates.front() + " ";
509  return;
510  }
511 
512  // Partial match, figure out the common substring
513  UString substring = findCommonSubstring(candidates);
514 
515  _completeHint.clear();
516  _completeHint.reserve(count);
517 
518  std::copy(candidates.begin(), candidates.end(), std::back_inserter(_completeHint));
519 
520  _maxHintSize = maxSize;
521 
522  if (substring != input) {
523  _currentLine = prefix + substring;
525  }
526 }
527 
528 UString ReadLine::findCommonSubstring(const std::list<UString> &strings) {
529  if (strings.empty())
530  return "";
531 
532  size_t minSize = strings.front().size();
533 
534  // Create iterators for all strings
535  std::list<UString::iterator> positions;
536  for (std::list<UString>::const_iterator s = strings.begin(); s != strings.end(); ++s) {
537  minSize = MIN(minSize, s->size());
538  if (minSize == 0)
539  return "";
540 
541  positions.push_back(s->begin());
542  }
543 
544  UString substring;
545 
546  while (minSize-- > 0) {
547  uint32 c = *positions.front();
548 
549  // Make sure the current character still matches in all strings
550  std::list<UString::iterator>::iterator p;
551  for (p = positions.begin(); p != positions.end(); ++(*p), ++p)
552  if (**p != c)
553  return substring;
554 
555  substring += c;
556  }
557 
558  return substring;
559 }
560 
561 bool ReadLine::isWordCharacter(uint32 c, bool onlySpace) {
562  if (onlySpace)
563  return c != ' ';
564 
565  return !UString::isASCII(c) || UString::isAlNum(c);
566 }
567 
568 size_t ReadLine::findLastWordStart(bool onlySpace) const {
570  if (pos == _currentLine.begin())
571  return _currentLine.getPosition(pos);
572 
573  --pos;
574 
575  // If we're between words, skip to the end of the last word
576  while ((pos != _currentLine.begin()) && !isWordCharacter(*pos, onlySpace))
577  --pos;
578 
579  // Now skip the word
580  while ((pos != _currentLine.begin()) && isWordCharacter(*pos, onlySpace))
581  --pos;
582 
583  if (pos != _currentLine.begin())
584  ++pos;
585 
586  // And return the position
587  return _currentLine.getPosition(pos);
588 }
589 
590 size_t ReadLine::findNextWordEnd(bool onlySpace) const {
592  if (pos == _currentLine.end())
593  return _currentLine.getPosition(pos);
594 
595  // If we're between words, skip to the start of the next word
596  while ((pos != _currentLine.end()) && !isWordCharacter(*pos, onlySpace))
597  ++pos;
598 
599  // Now skip the word
600  while ((pos != _currentLine.end()) && isWordCharacter(*pos, onlySpace))
601  ++pos;
602 
603  // And return the position
604  return _currentLine.getPosition(pos);
605 }
606 
607 } // End of namespace Common
size_t _historySizeCurrent
Current size of the history.
Definition: readline.h:103
ReadLine(size_t historySize)
Definition: readline.cpp:34
void insert(iterator pos, uint32 c)
Insert character c in front of this position.
Definition: ustring.cpp:513
void updateHistory()
Definition: readline.cpp:314
bool _overwrite
Overwrite instead of insert?
Definition: readline.h:111
Definition: 2dafile.h:39
std::list< UString >::iterator _historyPosition
The current browsing position within the history.
Definition: readline.h:119
ArgumentSets _arguments
All know tab-completable command arguments.
Definition: readline.h:127
A class holding an UTF-8 string.
Definition: ustring.h:48
bool _historyIgnoreSpace
Should we not remember input beginning with spaces?
Definition: readline.h:105
iterator getPosition(size_t n) const
Convert a numerical position into an iterator.
Definition: ustring.cpp:501
iterator begin() const
Definition: ustring.cpp:253
Text was written.
Definition: types.h:52
size_t _cursorPosition
The current cursor position.
Definition: readline.h:109
size_t findLastWordStart(bool onlySpace=false) const
Definition: readline.cpp:568
iterator findFirst(uint32 c) const
Definition: ustring.cpp:261
void addInput(uint32 c)
Add that character to the current input.
Definition: readline.cpp:99
std::set< UString > CommandSet
Definition: readline.h:98
SDL_Event Event
Definition: types.h:42
UString _currentLineBak
The backupped input line while we&#39;re browsing the history.
Definition: readline.h:114
bool processKeyDown(const Events::Event &event, UString &command)
Definition: readline.cpp:132
A class providing (limited) readline-like capabilities.
Keyboard key was pressed.
Definition: types.h:46
utf8::iterator< std::string::const_iterator > iterator
Definition: ustring.h:50
CommandSet _commands
All known tab-completable commands.
Definition: readline.h:125
#define UNUSED(x)
Definition: system.h:170
Utility templates and functions.
bool processEvent(const Events::Event &event, UString &command)
Process that given events.
Definition: readline.cpp:118
const std::vector< UString > & getCompleteHint(size_t &maxSize) const
Return the current tab-completion hints.
Definition: readline.cpp:93
The global events manager.
T MIN(T a, T b)
Definition: util.h:70
static bool isWordCharacter(uint32 c, bool onlySpace=false)
Definition: readline.cpp:561
void historyEraseDups(bool eraseDups)
Erase all lines matching the line to be saved.
Definition: readline.cpp:53
UString::iterator getCurrentPosition() const
Definition: readline.cpp:301
bool empty() const
Is the string empty?
Definition: ustring.cpp:245
void setArguments(const UString &command, const std::vector< UString > &arguments)
Set the tab-completable arguments for a command.
Definition: readline.cpp:66
bool _historyEraseDups
Should we actively remove duplicate lines?
Definition: readline.h:107
void historyIgnoreDups(bool ignoreDups)
Don&#39;t save lines matching the bottom of the history.
Definition: readline.cpp:49
void replace(iterator pos, uint32 c)
Replace the character at this position with c.
Definition: ustring.cpp:553
std::list< UString > _history
The history of previous input lines.
Definition: readline.h:117
size_t _historySizeMax
Max size of the history.
Definition: readline.h:102
#define EventMan
Shortcut for accessing the events manager.
Definition: events.h:210
void tabComplete()
Definition: readline.cpp:457
void historyIgnoreSpace(bool ignoreSpace)
Don&#39;t save lines starting with a space.
Definition: readline.cpp:45
void addCommand(const UString &command)
Add a command that can be tab-completed.
Definition: readline.cpp:62
size_t getCursorPosition() const
Return the current cursor position within the input line.
Definition: readline.cpp:85
bool getOverwrite() const
Return whether we&#39;re current in overwrite mode.
Definition: readline.cpp:89
void clearHistory()
Clear the input history.
Definition: readline.cpp:57
void trimLeft()
Definition: ustring.cpp:395
static bool isASCII(uint32 c)
Is the character an ASCII character?
Definition: ustring.cpp:785
size_t size() const
Return the size of the string, in characters.
Definition: ustring.cpp:241
uint32_t uint32
Definition: types.h:204
void addCurrentLineToHistory()
Definition: readline.cpp:330
size_t _maxHintSize
Max size of a current command candidates.
Definition: readline.h:132
void browseBottom()
Definition: readline.cpp:443
void erase(iterator from, iterator to)
Erase the character within this range.
Definition: ustring.cpp:598
std::vector< UString > _completeHint
Current possible command candidates for the input line.
Definition: readline.h:130
void split(iterator splitPoint, UString &left, UString &right, bool remove=false) const
Definition: ustring.cpp:621
iterator end() const
Definition: ustring.cpp:257
std::list< HistorySave > _historySave
Saved copies of modified history lines.
Definition: readline.h:122
bool _historyIgnoreDups
Should we not remember duplicate lines?
Definition: readline.h:106
size_t findNextWordEnd(bool onlySpace=false) const
Definition: readline.cpp:590
static UString findCommonSubstring(const std::list< UString > &strings)
Definition: readline.cpp:528
T MAX(T a, T b)
Definition: util.h:71
void clear()
Clear the string&#39;s contents.
Definition: ustring.cpp:236
UString _currentLine
The current input line.
Definition: readline.h:113
std::list< HistorySave >::iterator findHistorySave()
Definition: readline.cpp:305
static bool isAlNum(uint32 c)
Is the character an ASCII alphanumeric character?
Definition: ustring.cpp:801
bool processTextInput(const Events::Event &event, UString &command)
Definition: readline.cpp:284
const UString & getCurrentLine() const
Return the current input line.
Definition: readline.cpp:81