openMSX
CommandConsole.cc
Go to the documentation of this file.
1 #include "CommandConsole.hh"
2 #include "CommandException.hh"
4 #include "Completer.hh"
5 #include "Interpreter.hh"
6 #include "Keys.hh"
7 #include "FileContext.hh"
8 #include "FileException.hh"
9 #include "FileOperations.hh"
10 #include "CliComm.hh"
11 #include "InputEvents.hh"
12 #include "Display.hh"
13 #include "EventDistributor.hh"
14 #include "SDL.h"
15 #include "Version.hh"
16 #include "checked_cast.hh"
17 #include "utf8_unchecked.hh"
18 #include "StringOp.hh"
19 #include "ScopedAssign.hh"
20 #include "scope_exit.hh"
21 #include "view.hh"
22 #include "xrange.hh"
23 #include <algorithm>
24 #include <fstream>
25 #include <cassert>
26 
27 using std::min;
28 using std::max;
29 using std::string;
30 using std::string_view;
31 
32 namespace openmsx {
33 
34 // class ConsoleLine
35 
36 ConsoleLine::ConsoleLine(string line_, uint32_t rgb)
37  : line(std::move(line_))
38  , chunks(1, {rgb, 0})
39 {
40 }
41 
42 void ConsoleLine::addChunk(string_view text, uint32_t rgb)
43 {
44  chunks.emplace_back(rgb, line.size());
45  line.append(text.data(), text.size());
46 }
47 
48 size_t ConsoleLine::numChars() const
49 {
50  return utf8::unchecked::size(line);
51 }
52 
53 uint32_t ConsoleLine::chunkColor(size_t i) const
54 {
55  assert(i < chunks.size());
56  return chunks[i].first;
57 }
58 
59 string_view ConsoleLine::chunkText(size_t i) const
60 {
61  assert(i < chunks.size());
62  auto pos = chunks[i].second;
63  auto len = ((i + 1) == chunks.size())
64  ? string_view::npos
65  : chunks[i + 1].second - pos;
66  return string_view(line).substr(pos, len);
67 }
68 
69 ConsoleLine ConsoleLine::substr(size_t pos, size_t len) const
70 {
71  ConsoleLine result;
72  if (chunks.empty()) {
73  assert(line.empty());
74  assert(pos == 0);
75  return result;
76  }
77 
78  auto b = begin(line);
80  auto e = b;
81  while (len-- && (e != end(line))) {
83  }
84  result.line.assign(b, e);
85 
86  unsigned bpos = b - begin(line);
87  unsigned bend = e - begin(line);
88  unsigned i = 1;
89  while ((i < chunks.size()) && (chunks[i].second <= bpos)) {
90  ++i;
91  }
92  result.chunks.emplace_back(chunks[i - 1].first, 0);
93  while ((i < chunks.size()) && (chunks[i].second < bend)) {
94  result.chunks.emplace_back(chunks[i].first,
95  chunks[i].second - bpos);
96  ++i;
97  }
98  return result;
99 }
100 
101 // class CommandConsole
102 
103 constexpr const char* const PROMPT_NEW = "> ";
104 constexpr const char* const PROMPT_CONT = "| ";
105 constexpr const char* const PROMPT_BUSY = "*busy*";
106 
108  GlobalCommandController& commandController_,
109  EventDistributor& eventDistributor_,
110  Display& display_)
111  : commandController(commandController_)
112  , eventDistributor(eventDistributor_)
113  , display(display_)
114  , consoleSetting(
115  commandController, "console",
116  "turns console display on/off", false, Setting::DONT_SAVE)
117  , historySizeSetting(
118  commandController, "console_history_size",
119  "amount of commands kept in console history", 100, 0, 10000)
120  , removeDoublesSetting(
121  commandController, "console_remove_doubles",
122  "don't add the command to history if it's the same as the previous one",
123  true)
124  , history(std::max(1, historySizeSetting.getInt()))
125  , executingCommand(false)
126 {
127  resetScrollBack();
128  prompt = PROMPT_NEW;
129  newLineConsole(prompt);
130  loadHistory();
131  putPrompt();
132  Completer::setOutput(this);
133 
134  const auto& fullVersion = Version::full();
135  print(fullVersion);
136  print(string(fullVersion.size(), '-'));
137  print("\n"
138  "General information about openMSX is available at "
139  "http://openmsx.org.\n"
140  "\n"
141  "Type 'help' to see a list of available commands "
142  "(use <PgUp>/<PgDn> to scroll).\n"
143  "Or read the Console Command Reference in the manual.\n"
144  "\n");
145 
146  commandController.getInterpreter().setOutput(this);
147  eventDistributor.registerEventListener(
149  // also listen to KEY_UP events, so that we can consume them
150  eventDistributor.registerEventListener(
152 }
153 
155 {
156  eventDistributor.unregisterEventListener(OPENMSX_KEY_DOWN_EVENT, *this);
157  eventDistributor.unregisterEventListener(OPENMSX_KEY_UP_EVENT, *this);
158  commandController.getInterpreter().setOutput(nullptr);
159  Completer::setOutput(nullptr);
160 }
161 
162 void CommandConsole::saveHistory()
163 {
164  try {
165  std::ofstream outputfile;
166  FileOperations::openofstream(outputfile,
167  userFileContext("console").resolveCreate("history.txt"));
168  if (!outputfile) {
169  throw FileException(
170  "Error while saving the console history.");
171  }
172  for (auto& s : history) {
173  outputfile << s << '\n';
174  }
175  } catch (FileException& e) {
176  commandController.getCliComm().printWarning(e.getMessage());
177  }
178 }
179 
180 void CommandConsole::loadHistory()
181 {
182  try {
183  std::ifstream inputfile(
184  userFileContext("console").
185  resolveCreate("history.txt").c_str());
186  string line;
187  while (inputfile) {
188  getline(inputfile, line);
189  putCommandHistory(line);
190  }
191  } catch (FileException&) {
192  // Error while loading the console history, ignore
193  }
194 }
195 
196 void CommandConsole::getCursorPosition(unsigned& xPosition, unsigned& yPosition) const
197 {
198  xPosition = cursorPosition % getColumns();
199  auto num = lines[0].numChars() / getColumns();
200  yPosition = unsigned(num - (cursorPosition / getColumns()));
201 }
202 
204 {
205  size_t count = 0;
206  for (auto buf : xrange(lines.size())) {
207  count += (lines[buf].numChars() / getColumns()) + 1;
208  if (count > line) {
209  return lines[buf].substr(
210  (count - line - 1) * getColumns(),
211  getColumns());
212  }
213  }
214  return ConsoleLine();
215 }
216 
217 int CommandConsole::signalEvent(const std::shared_ptr<const Event>& event)
218 {
219  if (!consoleSetting.getBoolean()) return 0;
220  auto& keyEvent = checked_cast<const KeyEvent&>(*event);
221 
222  // If the console is open then don't pass the event to the MSX
223  // (whetever the (keyboard) event is). If the event has a meaning for
224  // the console, then also don't pass the event to the hotkey system.
225  // For example PgUp, PgDown are keys that have both a meaning in the
226  // console and are used by standard key bindings.
227  if (event->getType() == OPENMSX_KEY_DOWN_EVENT) {
228  if (!executingCommand) {
229  if (handleEvent(keyEvent)) {
230  // event was used
231  display.repaintDelayed(40000); // 25fps
232  return EventDistributor::HOTKEY; // block HOTKEY and MSX
233  }
234  } else {
235  // For commands that take a long time to execute (e.g.
236  // a loadstate that needs to create a filepool index),
237  // we also send events during the execution (so that
238  // we can show progress on the OSD). In that case
239  // ignore extra input events.
240  }
241  } else {
242  assert(event->getType() == OPENMSX_KEY_UP_EVENT);
243  }
244  return EventDistributor::MSX; // block MSX
245 }
246 
247 bool CommandConsole::handleEvent(const KeyEvent& keyEvent)
248 {
249  auto keyCode = keyEvent.getKeyCode();
250  int key = keyCode & Keys::K_MASK;
251  int mod = keyCode & ~Keys::K_MASK;
252 
253  switch (mod) {
254  case Keys::KM_CTRL:
255  switch (key) {
256  case Keys::K_H:
257  backspace();
258  return true;
259  case Keys::K_A:
260  cursorPosition = unsigned(prompt.size());
261  return true;
262  case Keys::K_E:
263  cursorPosition = unsigned(lines[0].numChars());
264  return true;
265  case Keys::K_C:
266  clearCommand();
267  return true;
268  case Keys::K_L:
269  clearHistory();
270  return true;
271 #ifndef __APPLE__
272  case Keys::K_V:
273  paste();
274  return true;
275 #endif
276  }
277  break;
278  case Keys::KM_SHIFT:
279  switch (key) {
280  case Keys::K_PAGEUP:
281  scroll(max<int>(getRows() - 1, 1));
282  return true;
283  case Keys::K_PAGEDOWN:
284  scroll(-max<int>(getRows() - 1, 1));
285  return true;
286  }
287  break;
288  case Keys::KM_META:
289  switch (key) {
290 #ifdef __APPLE__
291  case Keys::K_V:
292  paste();
293  return true;
294  case Keys::K_K:
295  clearHistory();
296  return true;
297  case Keys::K_LEFT:
298  cursorPosition = unsigned(prompt.size());
299  return true;
300  case Keys::K_RIGHT:
301  cursorPosition = unsigned(lines[0].numChars());
302  return true;
303 #endif
304  }
305  break;
306  case Keys::KM_ALT:
307  switch (key) {
308  case Keys::K_BACKSPACE:
309  deleteToStartOfWord();
310  return true;
311 #ifdef __APPLE__
312  case Keys::K_DELETE:
313  deleteToEndOfWord();
314  return true;
315 #else
316  case Keys::K_D:
317  deleteToEndOfWord();
318  return true;
319 #endif
320  case Keys::K_LEFT:
321  gotoStartOfWord();
322  return true;
323  case Keys::K_RIGHT:
324  gotoEndOfWord();
325  return true;
326  }
327  break;
328  case 0: // no modifier
329  switch (key) {
330  case Keys::K_PAGEUP:
331  scroll(1);
332  return true;
333  case Keys::K_PAGEDOWN:
334  scroll(-1);
335  return true;
336  case Keys::K_UP:
337  prevCommand();
338  return true;
339  case Keys::K_DOWN:
340  nextCommand();
341  return true;
342  case Keys::K_BACKSPACE:
343  backspace();
344  return true;
345  case Keys::K_DELETE:
346  delete_key();
347  return true;
348  case Keys::K_TAB:
349  tabCompletion();
350  return true;
351  case Keys::K_RETURN:
352  case Keys::K_KP_ENTER:
353  commandExecute();
354  cursorPosition = unsigned(prompt.size());
355  return true;
356  case Keys::K_LEFT:
357  if (cursorPosition > prompt.size()) {
358  --cursorPosition;
359  }
360  return true;
361  case Keys::K_RIGHT:
362  if (cursorPosition < lines[0].numChars()) {
363  ++cursorPosition;
364  }
365  return true;
366  case Keys::K_HOME:
367 #ifdef __APPLE__
368  scroll(lines.size());
369 #else
370  cursorPosition = unsigned(prompt.size());
371 #endif
372  return true;
373  case Keys::K_END:
374 #ifdef __APPLE__
375  scroll(-lines.size());
376 #else
377  cursorPosition = unsigned(lines[0].numChars());
378 #endif
379  return true;
380  }
381  break;
382  }
383 
384  auto unicode = keyEvent.getUnicode();
385  if (!unicode || (mod & Keys::KM_META)) {
386  // Disallow META modifer for 'normal' key presses because on
387  // MacOSX Cmd+L is used as a hotkey to toggle the console.
388  // Hopefully there are no systems that require META to type
389  // normal keys. However there _are_ systems that require the
390  // following modifiers, some examples:
391  // MODE: to type '1-9' on a N900
392  // ALT: to type | [ ] on a azerty keyboard layout
393  // CTRL+ALT: to type '#' on a spanish keyboard layout (windows)
394  //
395  // Event was not used by the console, allow the other
396  // subsystems to process it. E.g. F10, or Cmd+L to close the
397  // console.
398  return false;
399  }
400 
401  // Apparently on macOS keyboard events for keys like F1 have a non-zero
402  // unicode field. This confuses the console code (it thinks it's a
403  // printable character) and it prevents those keys from triggering
404  // hotkey bindings. See this bug report:
405  // https://github.com/openMSX/openMSX/issues/1095
406  // As a workaround we ignore chars in the 'Private Use Area' (PUA).
407  // https://en.wikipedia.org/wiki/Private_Use_Areas
408  if (utf8::is_pua(unicode)) {
409  return false;
410  }
411 
412  if (unicode >= 0x20) {
413  normalKey(unicode);
414  } else {
415  // Skip CTRL-<X> combinations, but still return true.
416  }
417  return true;
418 }
419 
420 void CommandConsole::output(string_view text)
421 {
422  print(text);
423 }
424 
425 unsigned CommandConsole::getOutputColumns() const
426 {
427  return getColumns();
428 }
429 
430 void CommandConsole::print(string_view text, unsigned rgb)
431 {
432  while (true) {
433  auto pos = text.find('\n');
434  newLineConsole(ConsoleLine(string(text.substr(0, pos)), rgb));
435  if (pos == string_view::npos) return;
436  text = text.substr(pos + 1); // skip newline
437  if (text.empty()) return;
438  }
439 }
440 
441 void CommandConsole::newLineConsole(string line)
442 {
443  newLineConsole(ConsoleLine(std::move(line)));
444 }
445 
446 void CommandConsole::newLineConsole(ConsoleLine line)
447 {
448  if (lines.isFull()) {
449  lines.removeBack();
450  }
451  ConsoleLine tmp = std::move(lines[0]);
452  lines[0] = std::move(line);
453  lines.addFront(std::move(tmp));
454 }
455 
456 void CommandConsole::putCommandHistory(const string& command)
457 {
458  if (command.empty()) return;
459  if (removeDoublesSetting.getBoolean() && !history.empty() &&
460  (history.back() == command)) {
461  return;
462  }
463  if (history.full()) history.pop_front();
464  history.push_back(command);
465 }
466 
467 void CommandConsole::commandExecute()
468 {
469  resetScrollBack();
470  string cmd0 = lines[0].str().substr(prompt.size());
471  putCommandHistory(cmd0);
472  saveHistory(); // save at this point already, so that we don't lose history in case of a crash
473 
474  strAppend(commandBuffer, cmd0, '\n');
475  newLineConsole(lines[0]);
476  if (commandController.isComplete(commandBuffer)) {
477  // Normally the busy prompt is NOT shown (not even very briefly
478  // because the screen is not redrawn), though for some commands
479  // that potentially take a long time to execute, we explictly
480  // send events, see also comment in signalEvent().
481  prompt = PROMPT_BUSY;
482  putPrompt();
483 
484  try {
485  ScopedAssign sa(executingCommand, true);
486  auto resultObj = commandController.executeCommand(
487  commandBuffer);
488  auto result = resultObj.getString();
489  if (!result.empty()) {
490  print(result);
491  }
492  } catch (CommandException& e) {
493  print(e.getMessage(), 0xff0000);
494  }
495  commandBuffer.clear();
496  prompt = PROMPT_NEW;
497  } else {
498  prompt = PROMPT_CONT;
499  }
500  putPrompt();
501 }
502 
503 ConsoleLine CommandConsole::highLight(string_view line)
504 {
505  ConsoleLine result;
506  result.addChunk(prompt, 0xffffff);
507 
508  TclParser parser = commandController.getInterpreter().parse(line);
509  string colors = parser.getColors();
510  assert(colors.size() == line.size());
511 
512  unsigned pos = 0;
513  while (pos != colors.size()) {
514  char col = colors[pos];
515  unsigned pos2 = pos++;
516  while ((pos != colors.size()) && (colors[pos] == col)) {
517  ++pos;
518  }
519  // TODO make these color configurable?
520  unsigned rgb;
521  switch (col) {
522  case 'E': rgb = 0xff0000; break; // error
523  case 'c': rgb = 0x5c5cff; break; // comment
524  case 'v': rgb = 0x00ffff; break; // variable
525  case 'l': rgb = 0xff00ff; break; // literal
526  case 'p': rgb = 0xcdcd00; break; // proc
527  case 'o': rgb = 0x00cdcd; break; // operator
528  default: rgb = 0xffffff; break; // other
529  }
530  result.addChunk(line.substr(pos2, pos - pos2), rgb);
531  }
532  return result;
533 }
534 
535 void CommandConsole::putPrompt()
536 {
537  commandScrollBack = unsigned(history.size());
538  currentLine.clear();
539  lines[0] = highLight(currentLine);
540  cursorPosition = unsigned(prompt.size());
541 }
542 
543 void CommandConsole::tabCompletion()
544 {
545  resetScrollBack();
546  auto pl = unsigned(prompt.size());
547  string front(utf8::unchecked::substr(lines[0].str(), pl, cursorPosition - pl));
548  string back (utf8::unchecked::substr(lines[0].str(), cursorPosition));
549  string newFront = commandController.tabCompletion(front);
550  cursorPosition = pl + unsigned(utf8::unchecked::size(newFront));
551  currentLine = newFront + back;
552  lines[0] = highLight(currentLine);
553 }
554 
555 void CommandConsole::scroll(int delta)
556 {
557  consoleScrollBack = max(min(consoleScrollBack + delta, int(lines.size()) - int(rows)), 0);
558 }
559 
560 // Returns the position of the start of the word relative to the current cursor position.
561 // If the cursor is already located at the start of a word then return the start of the previous word.
562 // If there is no previous word, then return the start of the line (position of the prompt).
563 //
564 // One complication is that the line is utf8 encoded and the positions are
565 // given in character-counts instead of byte-counts. To solve this this
566 // function returns a tuple:
567 // * an iterator to the start of the word (likely the new cursor position).
568 // * an iterator to the current cursor position.
569 // * the distance (in characters, not bytes) between these two iterators.
570 // The first item in this tuple is the actual result. The other two are only
571 // returned for efficiency (this function calculates them anyway, and it's
572 // likely the caller will need them as well).
573 static std::tuple<std::string::const_iterator, std::string::const_iterator, unsigned>
574  getStartOfWord(const std::string& line, unsigned cursorPos, unsigned promptSize)
575 {
576  auto begin = std::begin(line);
577  auto prompt = begin + promptSize; // assumes prompt only contains single-byte utf8 chars
578  auto cursor = begin; utf8::unchecked::advance(cursor, cursorPos);
579  auto pos = cursor;
580 
581  // search (backwards) for first non-white-space
582  unsigned distance = 0;
583  while (pos > prompt) {
584  auto pos2 = pos;
586  if (*pos2 != one_of(' ', '\t')) break;
587  pos = pos2;
588  ++distance;
589  }
590  // search (backwards) for first white-space
591  while (pos > prompt) {
592  auto pos2 = pos;
594  if (*pos2 == one_of(' ', '\t')) break;
595  pos = pos2;
596  ++distance;
597  }
598  return {pos, cursor, distance};
599 }
600 
601 // Similar to getStartOfWord() but locates the end of the word instead.
602 // The end of the word is the 2nd (instead of the 1st) element in the tuple.
603 // This is done so that both getStartOfWord() and getEndOfWord() have the invariant:
604 // result = [begin, end, distance]
605 // -> begin <= end
606 // -> end - begin == distance
607 static std::tuple<std::string::const_iterator, std::string::const_iterator, unsigned>
608  getEndOfWord(const std::string& line, unsigned cursorPos)
609 {
610  auto begin = std::begin(line);
611  auto end = std::end (line);
612  auto cursor = begin; utf8::unchecked::advance(cursor, cursorPos);
613  auto pos = cursor;
614 
615  // search (forwards) for first non-white-space
616  unsigned distance = 0;
617  while ((pos < end) && (*pos == one_of(' ', '\t'))) {
618  ++pos; // single-byte utf8 character
619  ++distance;
620  }
621  // search (forwards) for first white-space
622  while ((pos < end) && (*pos != one_of(' ', '\t'))) {
624  ++distance;
625  }
626  return {cursor, pos, distance};
627 }
628 
629 void CommandConsole::gotoStartOfWord()
630 {
631  auto [begin, end, distance] = getStartOfWord(lines[0].str(), cursorPosition, prompt.size());
632  cursorPosition -= distance;
633 }
634 
635 void CommandConsole::deleteToStartOfWord()
636 {
637  resetScrollBack();
638  currentLine = lines[0].str();
639  auto [begin, end, distance] = getStartOfWord(currentLine, cursorPosition, prompt.size());
640  currentLine.erase(begin, end);
641  currentLine.erase(0, prompt.size());
642  lines[0] = highLight(currentLine);
643  cursorPosition -= distance;
644 }
645 
646 void CommandConsole::gotoEndOfWord()
647 {
648  auto [begin, end, distance] = getEndOfWord(lines[0].str(), cursorPosition);
649  cursorPosition += distance;
650 }
651 
652 void CommandConsole::deleteToEndOfWord()
653 {
654  resetScrollBack();
655  currentLine = lines[0].str();
656  auto [begin, end, distance] = getEndOfWord(currentLine, cursorPosition);
657  currentLine.erase(begin, end);
658  currentLine.erase(0, prompt.size());
659  lines[0] = highLight(currentLine);
660 }
661 
662 void CommandConsole::prevCommand()
663 {
664  resetScrollBack();
665  if (history.empty()) {
666  return; // no elements
667  }
668  bool match = false;
669  unsigned tmp = commandScrollBack;
670  while ((tmp != 0) && !match) {
671  --tmp;
672  match = StringOp::startsWith(history[tmp], currentLine);
673  }
674  if (match) {
675  commandScrollBack = tmp;
676  lines[0] = highLight(history[commandScrollBack]);
677  cursorPosition = unsigned(lines[0].numChars());
678  }
679 }
680 
681 void CommandConsole::nextCommand()
682 {
683  resetScrollBack();
684  if (commandScrollBack == history.size()) {
685  return; // don't loop !
686  }
687  bool match = false;
688  auto tmp = commandScrollBack;
689  while ((++tmp != history.size()) && !match) {
690  match = StringOp::startsWith(history[tmp], currentLine);
691  }
692  if (match) {
693  --tmp; // one time to many
694  commandScrollBack = tmp;
695  lines[0] = highLight(history[commandScrollBack]);
696  } else {
697  commandScrollBack = unsigned(history.size());
698  lines[0] = highLight(currentLine);
699  }
700  cursorPosition = unsigned(lines[0].numChars());
701 }
702 
703 void CommandConsole::clearCommand()
704 {
705  resetScrollBack();
706  commandBuffer.clear();
707  prompt = PROMPT_NEW;
708  currentLine.clear();
709  lines[0] = highLight(currentLine);
710  cursorPosition = unsigned(prompt.size());
711 }
712 
713 void CommandConsole::clearHistory()
714 {
715  resetScrollBack();
716  while (lines.size() > 1) {
717  lines.removeBack();
718  }
719 }
720 
721 void CommandConsole::backspace()
722 {
723  resetScrollBack();
724  if (cursorPosition > prompt.size()) {
725  currentLine = lines[0].str();
726  auto b = begin(currentLine);
727  utf8::unchecked::advance(b, cursorPosition - 1);
728  auto e = b;
730  currentLine.erase(b, e);
731  currentLine.erase(0, prompt.size());
732  lines[0] = highLight(currentLine);
733  --cursorPosition;
734  }
735 }
736 
737 void CommandConsole::delete_key()
738 {
739  resetScrollBack();
740  if (lines[0].numChars() > cursorPosition) {
741  currentLine = lines[0].str();
742  auto b = begin(currentLine);
743  utf8::unchecked::advance(b, cursorPosition);
744  auto e = b;
746  currentLine.erase(b, e);
747  currentLine.erase(0, prompt.size());
748  lines[0] = highLight(currentLine);
749  }
750 }
751 
752 void CommandConsole::normalKey(uint32_t chr)
753 {
754  assert(chr);
755  resetScrollBack();
756  currentLine = lines[0].str();
757  auto pos = begin(currentLine);
758  utf8::unchecked::advance(pos, cursorPosition);
759  utf8::unchecked::append(chr, inserter(currentLine, pos));
760  currentLine.erase(0, prompt.size());
761  lines[0] = highLight(currentLine);
762  ++cursorPosition;
763 }
764 
765 void CommandConsole::resetScrollBack()
766 {
767  consoleScrollBack = 0;
768 }
769 
770 static std::vector<std::string_view> splitLines(std::string_view str)
771 {
772  // This differs from StringOp::split() in two ways:
773  // - If the input is an empty string, then the resulting vector
774  // contains 1 element which is the empty string (StringOp::split()
775  // would return an empty vector).
776  // - If the input ends on a newline character, then the final item in
777  // the result vector is an empty string (StringOp::split() would not
778  // include that last empty string).
779  // TODO can we come up with a good name for this function and move it
780  // to StringOp?
781  std::vector<std::string_view> result;
782  while (true) {
783  auto pos = str.find_first_of('\n');
784  if (pos == std::string_view::npos) break;
785  result.push_back(str.substr(0, pos));
786  str = str.substr(pos + 1);
787  }
788  result.push_back(str);
789  return result;
790 }
791 
792 void CommandConsole::paste()
793 {
794  char* text = SDL_GetClipboardText();
795  if (!text) return;
796  scope_exit e([&]{ SDL_free(text); });
797 
798  auto pastedLines = splitLines(text);
799  assert(!pastedLines.empty());
800 
801  // helper function 'append()' to append to the existing text
802  std::string_view prefix = lines[0].str();
803  prefix.remove_prefix(prompt.size());
804  auto append = [&](std::string_view suffix) {
805  if (prefix.empty()) {
806  lines[0] = highLight(suffix);
807  } else {
808  lines[0] = highLight(strCat(prefix, suffix));
809  prefix = "";
810  }
811  };
812  // execute all but the last line
813  for (const auto& line : view::drop_back(pastedLines, 1)) {
814  append(line);
815  commandExecute();
816  }
817  // only enter (not execute) the last line
818  append(pastedLines.back());
819  cursorPosition = unsigned(lines[0].numChars());
820 }
821 
822 } // namespace openmsx
FileException.hh
TclParser
Definition: TclParser.hh:11
openmsx::PROMPT_CONT
constexpr const char *const PROMPT_CONT
Definition: CommandConsole.cc:104
utf8::is_pua
bool is_pua(uint32_t cp)
Definition: utf8_core.hh:256
openmsx::GlobalCommandController::getInterpreter
Interpreter & getInterpreter() override
Definition: GlobalCommandController.cc:123
scope_exit
Definition: scope_exit.hh:25
openmsx::Keys::KM_META
@ KM_META
Definition: Keys.hh:207
xrange
auto xrange(T e)
Definition: xrange.hh:170
circular_buffer::pop_front
void pop_front()
Definition: circular_buffer.hh:231
StringOp::startsWith
bool startsWith(string_view total, string_view part)
Definition: StringOp.cc:71
openmsx::Interpreter::parse
TclParser parse(std::string_view command)
Definition: Interpreter.cc:447
gl::min
vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:274
Display.hh
openmsx::Keys::K_A
@ K_A
Definition: Keys.hh:76
ScopedAssign
Assign new value to some variable and restore the original value when this object goes out of scope.
Definition: ScopedAssign.hh:7
TclParser::getColors
std::string getColors() const
Ouput: a string of equal length of the input command where each character indicates the type of the c...
Definition: TclParser.hh:28
openmsx::CommandConsole::getColumns
unsigned getColumns() const
Definition: CommandConsole.hh:80
utf8::unchecked::next
uint32_t next(octet_iterator &it)
Definition: utf8_unchecked.hh:64
openmsx::GlobalCommandController::getCliComm
CliComm & getCliComm() override
Definition: GlobalCommandController.cc:118
view::drop_back
auto drop_back(Range &&range, size_t n)
Definition: view.hh:294
utf8::unchecked::size
size_t size(std::string_view utf8)
Definition: utf8_unchecked.hh:227
scope_exit.hh
openmsx::CommandConsole::~CommandConsole
~CommandConsole()
Definition: CommandConsole.cc:154
utf8::unchecked::substr
std::string_view substr(std::string_view utf8, std::string_view::size_type first=0, std::string_view::size_type len=std::string_view::npos)
Definition: utf8_unchecked.hh:231
openmsx::GlobalCommandController::executeCommand
TclObject executeCommand(const std::string &command, CliConnection *connection=nullptr) override
Execute the given command.
Definition: GlobalCommandController.cc:301
LZ4::count
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
Definition: lz4.cc:207
openmsx::GlobalCommandController::tabCompletion
std::string tabCompletion(std::string_view command)
Complete the given command.
Definition: GlobalCommandController.cc:319
openmsx::ConsoleLine::substr
ConsoleLine substr(size_t pos, size_t len) const
Get a part of total line.
Definition: CommandConsole.cc:69
openmsx::Keys::K_C
@ K_C
Definition: Keys.hh:78
Interpreter.hh
circular_buffer::push_back
void push_back(const T &t)
Definition: circular_buffer.hh:216
openmsx::userFileContext
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:161
CommandConsole.hh
openmsx::Keys::K_RIGHT
@ K_RIGHT
Definition: Keys.hh:126
utf8::unchecked::advance
void advance(octet_iterator &it, distance_type n)
Definition: utf8_unchecked.hh:108
strAppend
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:644
openmsx::OPENMSX_KEY_DOWN_EVENT
@ OPENMSX_KEY_DOWN_EVENT
Definition: Event.hh:13
openmsx::FileOperations::openofstream
void openofstream(std::ofstream &stream, const std::string &filename)
Open an ofstream in a platform-independent manner.
Definition: FileOperations.cc:367
openmsx::Setting
Definition: Setting.hh:119
openmsx::EventDistributor
Definition: EventDistributor.hh:16
ScopedAssign.hh
openmsx::ConsoleLine::chunkColor
uint32_t chunkColor(size_t i) const
Get the color for the i-th chunk.
Definition: CommandConsole.cc:53
openmsx::Display::repaintDelayed
void repaintDelayed(uint64_t delta)
Definition: Display.cc:367
circular_buffer::empty
bool empty() const
Definition: circular_buffer.hh:196
Version.hh
circular_buffer::back
auto & back()
Definition: circular_buffer.hh:192
openmsx::Interpreter::setOutput
void setOutput(InterpreterOutput *output_)
Definition: Interpreter.hh:25
openmsx::PROMPT_NEW
constexpr const char *const PROMPT_NEW
Definition: CommandConsole.cc:103
openmsx::CliComm::printWarning
void printWarning(std::string_view message)
Definition: CliComm.cc:10
openmsx::EventDistributor::HOTKEY
@ HOTKEY
Definition: EventDistributor.hh:27
openmsx::Keys::K_H
@ K_H
Definition: Keys.hh:83
openmsx::Keys::K_DOWN
@ K_DOWN
Definition: Keys.hh:125
openmsx::EventDistributor::MSX
@ MSX
Definition: EventDistributor.hh:28
utf8_unchecked.hh
openmsx::CommandConsole::getLine
ConsoleLine getLine(unsigned line) const
Definition: CommandConsole.cc:203
openmsx::ConsoleLine::addChunk
void addChunk(std::string_view text, uint32_t rgb)
Append a chunk with a (different) color.
Definition: CommandConsole.cc:42
openmsx::Keys::KM_ALT
@ KM_ALT
Definition: Keys.hh:206
one_of
Definition: one_of.hh:7
EventDistributor.hh
utf8::unchecked::append
octet_iterator append(uint32_t cp, octet_iterator result)
Definition: utf8_unchecked.hh:39
InputEvents.hh
openmsx::Keys::K_D
@ K_D
Definition: Keys.hh:79
utf8::distance
auto distance(octet_iterator first, octet_iterator last)
Definition: utf8_checked.hh:194
openmsx::FileException
Definition: FileException.hh:8
openmsx::Keys::KM_CTRL
@ KM_CTRL
Definition: Keys.hh:205
openmsx::Keys::K_DELETE
@ K_DELETE
Definition: Keys.hh:102
circular_buffer::full
bool full() const
Definition: circular_buffer.hh:197
view.hh
Keys.hh
openmsx::Keys::K_BACKSPACE
@ K_BACKSPACE
Definition: Keys.hh:31
FileContext.hh
openmsx::EventDistributor::CONSOLE
@ CONSOLE
Definition: EventDistributor.hh:26
FileOperations.hh
checked_cast.hh
openmsx::Keys::K_KP_ENTER
@ K_KP_ENTER
Definition: Keys.hh:120
openmsx::Keys::K_K
@ K_K
Definition: Keys.hh:86
openmsx::Keys::KM_SHIFT
@ KM_SHIFT
Definition: Keys.hh:204
openmsx::CommandConsole::CommandConsole
CommandConsole(GlobalCommandController &commandController, EventDistributor &eventDistributor, Display &display)
Definition: CommandConsole.cc:107
openmsx::Keys::K_V
@ K_V
Definition: Keys.hh:97
openmsx::Keys::K_UP
@ K_UP
Definition: Keys.hh:124
openmsx::ConsoleLine
This class represents a single text line in the console.
Definition: CommandConsole.hh:22
StringOp.hh
openmsx::Keys::K_L
@ K_L
Definition: Keys.hh:87
openmsx::Display
Represents the output window/screen of openMSX.
Definition: Display.hh:31
openmsx::Keys::K_LEFT
@ K_LEFT
Definition: Keys.hh:127
GlobalCommandController.hh
openmsx::TclObject::getString
std::string_view getString() const
Definition: TclObject.cc:102
openmsx::GlobalCommandController::isComplete
bool isComplete(const std::string &command)
Returns true iff the command is complete (all braces, quotes etc.
Definition: GlobalCommandController.cc:296
openmsx::ConsoleLine::numChars
size_t numChars() const
Get the number of UTF8 characters in this line.
Definition: CommandConsole.cc:48
openmsx::Keys::K_RETURN
@ K_RETURN
Definition: Keys.hh:34
detail::append
void append(Result &)
Definition: stl.hh:340
gl::max
vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:292
openmsx::Version::full
static std::string full()
Definition: Version.cc:8
openmsx::OPENMSX_KEY_UP_EVENT
@ OPENMSX_KEY_UP_EVENT
Definition: Event.hh:12
openmsx::EventDistributor::registerEventListener
void registerEventListener(EventType type, EventListener &listener, Priority priority=OTHER)
Registers a given object to receive certain events.
Definition: EventDistributor.cc:23
CliComm.hh
openmsx::Keys::K_PAGEDOWN
@ K_PAGEDOWN
Definition: Keys.hh:132
openmsx::CommandConsole::getCursorPosition
void getCursorPosition(unsigned &xPosition, unsigned &yPosition) const
Definition: CommandConsole.cc:196
openmsx::EventDistributor::unregisterEventListener
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
Definition: EventDistributor.cc:35
openmsx::Keys::K_TAB
@ K_TAB
Definition: Keys.hh:32
strCat
std::string strCat(Ts &&...ts)
Definition: strCat.hh:573
CommandException.hh
openmsx::GlobalCommandController
Definition: GlobalCommandController.hh:35
openmsx::ConsoleLine::ConsoleLine
ConsoleLine()=default
Construct empty line.
openmsx
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
circular_buffer::size
size_t size() const
Definition: circular_buffer.hh:195
openmsx::Keys::K_MASK
@ K_MASK
Definition: Keys.hh:27
openmsx::Keys::K_PAGEUP
@ K_PAGEUP
Definition: Keys.hh:131
openmsx::BooleanSetting::getBoolean
bool getBoolean() const noexcept
Definition: BooleanSetting.hh:17
xrange.hh
openmsx::Keys::K_HOME
@ K_HOME
Definition: Keys.hh:129
openmsx::PROMPT_BUSY
constexpr const char *const PROMPT_BUSY
Definition: CommandConsole.cc:105
openmsx::CommandConsole::getRows
unsigned getRows() const
Definition: CommandConsole.hh:82
utf8::unchecked::prior
uint32_t prior(octet_iterator &it)
Definition: utf8_unchecked.hh:100
Completer.hh
openmsx::ConsoleLine::chunkText
std::string_view chunkText(size_t i) const
Get the text for the i-th chunk.
Definition: CommandConsole.cc:59
openmsx::Keys::K_END
@ K_END
Definition: Keys.hh:130
openmsx::Completer::setOutput
static void setOutput(InterpreterOutput *output_)
Definition: Completer.hh:65
openmsx::Keys::K_E
@ K_E
Definition: Keys.hh:80