openMSX
GlobalCommandController.cc
Go to the documentation of this file.
2 #include "Reactor.hh"
3 #include "Setting.hh"
4 #include "ProxyCommand.hh"
5 #include "ProxySetting.hh"
6 #include "LocalFileReference.hh"
7 #include "GlobalCliComm.hh"
8 #include "CliConnection.hh"
9 #include "CommandException.hh"
10 #include "SettingsManager.hh"
11 #include "TclObject.hh"
12 #include "Version.hh"
13 #include "ScopedAssign.hh"
14 #include "join.hh"
15 #include "outer.hh"
16 #include "ranges.hh"
17 #include "stl.hh"
18 #include "StringOp.hh"
19 #include "view.hh"
20 #include "xrange.hh"
21 #include "build-info.hh"
22 #include <cassert>
23 #include <memory>
24 
25 using std::string;
26 using std::string_view;
27 using std::vector;
28 
29 namespace openmsx {
30 
32  EventDistributor& eventDistributor,
33  GlobalCliComm& cliComm_, Reactor& reactor_)
34  : cliComm(cliComm_)
35  , connection(nullptr)
36  , reactor(reactor_)
37  , openMSXInfoCommand(*this, "openmsx_info")
38  , hotKey(reactor.getRTScheduler(), *this, eventDistributor)
39  , settingsConfig(*this, hotKey)
40  , helpCmd(*this)
41  , tabCompletionCmd(*this)
42  , updateCmd(*this)
43  , platformInfo(getOpenMSXInfoCommand())
44  , versionInfo (getOpenMSXInfoCommand())
45  , romInfoTopic(getOpenMSXInfoCommand())
46 {
47 }
48 
50 
52 {
53  // GlobalCommandController destructor must have run before
54  // we can check this.
55 #ifdef DEBUG
56  assert(commands.empty());
57 #endif
58  assert(commandCompleters.empty());
59 }
60 
62 {
63  auto it = proxyCommandMap.find(name);
64  if (it == end(proxyCommandMap)) {
65  it = proxyCommandMap.emplace_noDuplicateCheck(
66  0, std::make_unique<ProxyCmd>(reactor, name));
67  }
68  ++it->first;
69 }
70 
72 {
73  auto it = proxyCommandMap.find(name);
74  assert(it != end(proxyCommandMap));
75  assert(it->first > 0);
76  --it->first;
77  if (it->first == 0) {
78  proxyCommandMap.erase(it);
79  }
80 }
81 
82 GlobalCommandController::ProxySettings::iterator
83 GlobalCommandController::findProxySetting(string_view name)
84 {
85  return ranges::find(proxySettings, name,
86  [](auto& v) { return v.first->getFullName(); });
87 }
88 
90 {
91  const auto& name = setting.getBaseNameObj();
92  auto it = findProxySetting(name.getString());
93  if (it == end(proxySettings)) {
94  // first occurrence
95  auto proxy = std::make_unique<ProxySetting>(reactor, name);
98  proxySettings.emplace_back(std::move(proxy), 1);
99  } else {
100  // was already registered
101  ++(it->second);
102  }
103 }
104 
106 {
107  auto it = findProxySetting(setting.getBaseName());
108  assert(it != end(proxySettings));
109  assert(it->second);
110  --(it->second);
111  if (it->second == 0) {
112  auto& proxy = *it->first;
115  move_pop_back(proxySettings, it);
116  }
117 }
118 
120 {
121  return cliComm;
122 }
123 
125 {
126  return interpreter;
127 }
128 
130  Command& command, zstring_view str)
131 {
132 #ifdef DEBUG
133  assert(!commands.contains(str));
134  commands.emplace_noDuplicateCheck(str, &command);
135 #endif
136  interpreter.registerCommand(str, command);
137 }
138 
140  Command& command, string_view str)
141 {
142  (void)str;
143 #ifdef DEBUG
144  assert(commands.contains(str));
145  assert(commands[str] == &command);
146  commands.erase(str);
147 #endif
148  interpreter.unregisterCommand(command);
149 }
150 
152  CommandCompleter& completer, string_view str)
153 {
154  if (StringOp::startsWith(str, "::")) str.remove_prefix(2); // drop leading ::
155  assert(!commandCompleters.contains(str));
156  commandCompleters.emplace_noDuplicateCheck(str, &completer);
157 }
158 
160  CommandCompleter& completer, string_view str)
161 {
162  if (StringOp::startsWith(str, "::")) str.remove_prefix(2); // drop leading ::
163  assert(commandCompleters.contains(str));
164  assert(commandCompleters[str] == &completer); (void)completer;
165  commandCompleters.erase(str);
166 }
167 
169 {
171  interpreter.registerSetting(setting);
172 }
173 
175 {
176  interpreter.unregisterSetting(setting);
178 }
179 
180 static vector<string> split(string_view str, const char delimiter)
181 {
182  vector<string> tokens;
183 
184  enum ParseState {Alpha, BackSlash, Quote};
185  ParseState state = Alpha;
186 
187  for (auto chr : str) {
188  switch (state) {
189  case Alpha:
190  if (tokens.empty()) {
191  tokens.emplace_back();
192  }
193  if (chr == delimiter) {
194  // token done, start new token
195  tokens.emplace_back();
196  } else {
197  tokens.back() += chr;
198  if (chr == '\\') {
199  state = BackSlash;
200  } else if (chr == '"') {
201  state = Quote;
202  }
203  }
204  break;
205  case Quote:
206  tokens.back() += chr;
207  if (chr == '"') {
208  state = Alpha;
209  }
210  break;
211  case BackSlash:
212  tokens.back() += chr;
213  state = Alpha;
214  break;
215  }
216  }
217  return tokens;
218 }
219 
220 static string removeEscaping(const string& str)
221 {
222  enum ParseState {Alpha, BackSlash, Quote};
223  ParseState state = Alpha;
224 
225  string result;
226  for (auto chr : str) {
227  switch (state) {
228  case Alpha:
229  if (chr == '\\') {
230  state = BackSlash;
231  } else if (chr == '"') {
232  state = Quote;
233  } else {
234  result += chr;
235  }
236  break;
237  case Quote:
238  if (chr == '"') {
239  state = Alpha;
240  } else {
241  result += chr;
242  }
243  break;
244  case BackSlash:
245  result += chr;
246  state = Alpha;
247  break;
248  }
249  }
250  return result;
251 }
252 
253 static vector<string> removeEscaping(span<const string> input, bool keepLastIfEmpty)
254 {
255  vector<string> result;
256  for (const auto& s : input) {
257  if (!s.empty()) {
258  result.push_back(removeEscaping(s));
259  }
260  }
261  if (keepLastIfEmpty && (input.empty() || input.back().empty())) {
262  result.emplace_back();
263  }
264  return result;
265 }
266 
267 static string escapeChars(const string& str, string_view chars)
268 {
269  string result;
270  for (auto chr : str) {
271  if (chars.find(chr) != string::npos) {
272  result += '\\';
273  }
274  result += chr;
275 
276  }
277  return result;
278 }
279 
280 static string addEscaping(const string& str, bool quote, bool finished)
281 {
282  if (str.empty() && finished) {
283  quote = true;
284  }
285  string result = escapeChars(str, "$[]");
286  if (quote) {
287  result.insert(result.begin(), '"');
288  if (finished) {
289  result += '"';
290  }
291  } else {
292  result = escapeChars(result, " ");
293  }
294  return result;
295 }
296 
298 {
299  return interpreter.isComplete(command);
300 }
301 
303  zstring_view command, CliConnection* connection_)
304 {
305  ScopedAssign sa(connection, connection_);
306  return interpreter.execute(command);
307 }
308 
309 void GlobalCommandController::source(const string& script)
310 {
311  try {
312  LocalFileReference file(script);
313  interpreter.executeFile(file.getFilename());
314  } catch (CommandException& e) {
316  "While executing ", script, ": ", e.getMessage());
317  }
318 }
319 
320 string GlobalCommandController::tabCompletion(string_view command)
321 {
322  // split on 'active' command (the command that should actually be
323  // completed). Some examples:
324  // if {[debug rea<tab> <-- should complete the 'debug' command
325  // instead of the 'if' command
326  // bind F6 { cycl<tab> <-- should complete 'cycle' instead of 'bind'
327  TclParser parser = interpreter.parse(command);
328  int last = parser.getLast();
329  string_view pre = command.substr(0, last);
330  string_view post = command.substr(last);
331 
332  // split command string in tokens
333  vector<string> originalTokens = split(post, ' ');
334  if (originalTokens.empty()) {
335  originalTokens.emplace_back();
336  }
337 
338  // complete last token
339  auto tokens = removeEscaping(originalTokens, true);
340  auto oldNum = tokens.size();
341  tabCompletion(tokens);
342  auto newNum = tokens.size();
343  bool tokenFinished = oldNum != newNum;
344 
345  // replace last token
346  string& original = originalTokens.back();
347  string& completed = tokens[oldNum - 1];
348  if (!completed.empty()) {
349  bool quote = !original.empty() && (original[0] == '"');
350  original = addEscaping(completed, quote, tokenFinished);
351  }
352  if (tokenFinished) {
353  assert(newNum == (oldNum + 1));
354  assert(tokens.back().empty());
355  originalTokens.emplace_back();
356  }
357 
358  // rebuild command string
359  return strCat(pre, join(originalTokens, ' '));
360 }
361 
362 void GlobalCommandController::tabCompletion(vector<string>& tokens)
363 {
364  if (tokens.empty()) {
365  // nothing typed yet
366  return;
367  }
368  if (tokens.size() == 1) {
369  string_view cmd = tokens[0];
370  string_view leadingNs;
371  // remove leading ::
372  if (StringOp::startsWith(cmd, "::")) {
373  cmd.remove_prefix(2);
374  leadingNs = "::";
375  }
376  // get current (typed) namespace
377  auto p1 = cmd.rfind("::");
378  string_view ns = (p1 == string_view::npos) ? cmd : cmd.substr(0, p1 + 2);
379 
380  // build a list of all command strings
381  TclObject names = interpreter.getCommandNames();
382  vector<string> names2;
383  names2.reserve(names.size());
384  for (string_view n1 : names) {
385  // remove leading ::
386  if (StringOp::startsWith(n1, "::")) n1.remove_prefix(2);
387  // initial namespace part must match
388  if (!StringOp::startsWith(n1, ns)) continue;
389  // the part following the initial namespace
390  string_view n2 = n1.substr(ns.size());
391  // only keep upto the next namespace portion,
392  auto p2 = n2.find("::");
393  auto n3 = (p2 == string_view::npos) ? n1 : n1.substr(0, ns.size() + p2 + 2);
394  // don't care about adding the same string multiple times
395  names2.push_back(strCat(leadingNs, n3));
396  }
397  Completer::completeString(tokens, names2);
398  } else {
399  string_view cmd = tokens.front();
400  if (StringOp::startsWith(cmd, "::")) cmd.remove_prefix(2); // drop leading ::
401 
402  if (auto* v = lookup(commandCompleters, cmd)) {
403  (*v)->tabCompletion(tokens);
404  } else {
405  TclObject command = makeTclList("openmsx::tabcompletion");
406  command.addListElements(tokens);
407  try {
408  TclObject list = command.executeCommand(interpreter);
409  bool sensitive = true;
410  auto begin = list.begin();
411  auto end = list.end();
412  if (begin != end) {
413  auto it2 = end; --it2;
414  auto back = *it2;
415  if (back == "false") {
416  end = it2;
417  sensitive = false;
418  } else if (back == "true") {
419  end = it2;
420  sensitive = true;
421  }
422  }
423  Completer::completeString(tokens, begin, end, sensitive);
424  } catch (CommandException& e) {
425  cliComm.printWarning(
426  "Error while executing tab-completion "
427  "proc: ", e.getMessage());
428  }
429  }
430  }
431 }
432 
433 
434 // Help Command
435 
436 GlobalCommandController::HelpCmd::HelpCmd(GlobalCommandController& controller_)
437  : Command(controller_, "help")
438 {
439 }
440 
441 void GlobalCommandController::HelpCmd::execute(
442  span<const TclObject> tokens, TclObject& result)
443 {
444  auto& controller = OUTER(GlobalCommandController, helpCmd);
445  switch (tokens.size()) {
446  case 1: {
447  string text =
448  "Use 'help [command]' to get help for a specific command\n"
449  "The following commands exist:\n";
450  auto cmds = concat<string_view>(
451  view::keys(controller.commandCompleters),
452  getInterpreter().execute("openmsx::all_command_names_with_help"));
453  cmds.erase(ranges::remove_if(cmds, [](const auto& c) {
454  return c.find("::") != std::string_view::npos; }),
455  cmds.end());
456  ranges::sort(cmds);
457  for (auto& line : formatListInColumns(cmds)) {
458  strAppend(text, line, '\n');
459  }
460  result = text;
461  break;
462  }
463  default: {
464  if (const auto* v = lookup(controller.commandCompleters, tokens[1].getString())) {
465  result = (*v)->help(tokens.subspan(1));
466  } else {
467  TclObject command = makeTclList("openmsx::help");
468  command.addListElements(view::drop(tokens, 1));
469  result = command.executeCommand(getInterpreter());
470  }
471  break;
472  }
473  }
474 }
475 
476 string GlobalCommandController::HelpCmd::help(span<const TclObject> /*tokens*/) const
477 {
478  return "prints help information for commands\n";
479 }
480 
481 void GlobalCommandController::HelpCmd::tabCompletion(vector<string>& tokens) const
482 {
483  string front = std::move(tokens.front());
484  tokens.erase(begin(tokens));
485  auto& controller = OUTER(GlobalCommandController, helpCmd);
486  controller.tabCompletion(tokens);
487  tokens.insert(begin(tokens), std::move(front));
488 }
489 
490 
491 // TabCompletionCmd Command
492 
493 GlobalCommandController::TabCompletionCmd::TabCompletionCmd(
494  GlobalCommandController& controller_)
495  : Command(controller_, "tabcompletion")
496 {
497 }
498 
499 void GlobalCommandController::TabCompletionCmd::execute(
500  span<const TclObject> tokens, TclObject& result)
501 {
502  checkNumArgs(tokens, 2, "commandstring");
503  // TODO this prints list of possible completions in the console
504  auto& controller = OUTER(GlobalCommandController, tabCompletionCmd);
505  result = controller.tabCompletion(tokens[1].getString());
506 }
507 
508 string GlobalCommandController::TabCompletionCmd::help(span<const TclObject> /*tokens*/) const
509 {
510  return "!!! This command will change in the future !!!\n"
511  "Tries to completes the given argument as if it were typed in "
512  "the console. This command is only useful to provide "
513  "tabcompletion to external console interfaces.";
514 }
515 
516 
517 // class UpdateCmd
518 
519 GlobalCommandController::UpdateCmd::UpdateCmd(CommandController& commandController_)
520  : Command(commandController_, "openmsx_update")
521 {
522 }
523 
524 static GlobalCliComm::UpdateType getType(const TclObject& name)
525 {
526  auto updateStr = CliComm::getUpdateStrings();
527  for (auto i : xrange(updateStr.size())) {
528  if (updateStr[i] == name) {
529  return static_cast<CliComm::UpdateType>(i);
530  }
531  }
532  throw CommandException("No such update type: ", name.getString());
533 }
534 
535 CliConnection& GlobalCommandController::UpdateCmd::getConnection()
536 {
537  auto& controller = OUTER(GlobalCommandController, updateCmd);
538  if (auto* c = controller.getConnection()) {
539  return *c;
540  }
541  throw CommandException("This command only makes sense when "
542  "it's used from an external application.");
543 }
544 
545 void GlobalCommandController::UpdateCmd::execute(
546  span<const TclObject> tokens, TclObject& /*result*/)
547 {
548  checkNumArgs(tokens, 3, Prefix{1}, "enable|disable type");
549  if (tokens[1] == "enable") {
550  getConnection().setUpdateEnable(getType(tokens[2]), true);
551  } else if (tokens[1] == "disable") {
552  getConnection().setUpdateEnable(getType(tokens[2]), false);
553  } else {
554  throw SyntaxError();
555  }
556 }
557 
558 string GlobalCommandController::UpdateCmd::help(span<const TclObject> /*tokens*/) const
559 {
560  return "Enable or disable update events for external applications. See doc/openmsx-control-xml.txt.";
561 }
562 
563 void GlobalCommandController::UpdateCmd::tabCompletion(vector<string>& tokens) const
564 {
565  switch (tokens.size()) {
566  case 2: {
567  using namespace std::literals;
568  static constexpr std::array ops = {"enable"sv, "disable"sv};
569  completeString(tokens, ops);
570  break;
571  }
572  case 3:
573  completeString(tokens, CliComm::getUpdateStrings());
574  break;
575  }
576 }
577 
578 
579 // Platform info
580 
581 GlobalCommandController::PlatformInfo::PlatformInfo(InfoCommand& openMSXInfoCommand_)
582  : InfoTopic(openMSXInfoCommand_, "platform")
583 {
584 }
585 
586 void GlobalCommandController::PlatformInfo::execute(
587  span<const TclObject> /*tokens*/, TclObject& result) const
588 {
589  result = TARGET_PLATFORM;
590 }
591 
592 string GlobalCommandController::PlatformInfo::help(span<const TclObject> /*tokens*/) const
593 {
594  return "Prints openMSX platform.";
595 }
596 
597 // Version info
598 
599 GlobalCommandController::VersionInfo::VersionInfo(InfoCommand& openMSXInfoCommand_)
600  : InfoTopic(openMSXInfoCommand_, "version")
601 {
602 }
603 
604 void GlobalCommandController::VersionInfo::execute(
605  span<const TclObject> /*tokens*/, TclObject& result) const
606 {
607  result = Version::full();
608 }
609 
610 string GlobalCommandController::VersionInfo::help(span<const TclObject> /*tokens*/) const
611 {
612  return "Prints openMSX version.";
613 }
614 
615 } // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:27
Assign new value to some variable and restore the original value when this object goes out of scope.
Definition: ScopedAssign.hh:8
int getLast() const
Get Start of the last subcommand.
Definition: TclParser.hh:32
static span< const char *const > getUpdateStrings()
Definition: CliComm.hh:88
void printWarning(std::string_view message)
Definition: CliComm.cc:10
static void completeString(std::vector< std::string > &tokens, ITER begin, ITER end, bool caseSensitive=true)
Definition: Completer.hh:133
hash_map< std::string, CommandCompleter *, XXHasher > commandCompleters
GlobalCommandController(const GlobalCommandController &)=delete
void registerSetting(Setting &setting) override
TODO.
std::string tabCompletion(std::string_view command)
Complete the given command.
void registerProxyCommand(std::string_view name)
void unregisterProxyCommand(std::string_view name)
void registerCommand(Command &command, zstring_view str) override
(Un)register a command
void registerCompleter(CommandCompleter &completer, std::string_view str) override
(Un)register a command completer, used to complete build-in Tcl cmds
TclObject executeCommand(zstring_view command, CliConnection *connection=nullptr) override
Execute the given command.
void unregisterSetting(Setting &setting) override
void unregisterCompleter(CommandCompleter &completer, std::string_view str) override
void unregisterCommand(Command &command, std::string_view str) override
bool isComplete(zstring_view command)
Returns true iff the command is complete (all braces, quotes etc.
void source(const std::string &script)
Executes all defined auto commands.
TclObject execute(zstring_view command)
Definition: Interpreter.cc:216
TclObject getCommandNames()
Definition: Interpreter.cc:206
void registerSetting(BaseSetting &variable)
Definition: Interpreter.cc:270
void unregisterCommand(Command &command)
Definition: Interpreter.cc:163
TclParser parse(std::string_view command)
Definition: Interpreter.cc:456
TclObject executeFile(zstring_view filename)
Definition: Interpreter.cc:225
bool isComplete(zstring_view command) const
Definition: Interpreter.cc:211
void unregisterSetting(BaseSetting &variable)
Definition: Interpreter.cc:322
void registerCommand(zstring_view name, Command &command)
Definition: Interpreter.cc:155
Helper class to use files in APIs other than openmsx::File.
const std::string & getFilename() const
Returns path to a local uncompressed version of this file.
const std::string & getMessage() const &
Definition: MSXException.hh:23
Contains the main loop of openMSX.
Definition: Reactor.hh:68
void unregisterSetting(BaseSetting &setting)
void registerSetting(BaseSetting &setting)
static std::string full()
Definition: Version.cc:8
Definition: span.hh:126
constexpr subspan_return_t< Offset, Count > subspan() const
Definition: span.hh:266
constexpr index_type size() const noexcept
Definition: span.hh:296
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
Definition: zstring_view.hh:22
mat3 n3(vec3(1, 0, 3), vec3(4, 5, 6), vec3(7, 8, 9))
const Value * lookup(const hash_map< Key, Value, Hasher, Equal > &map, const Key2 &key)
Definition: hash_map.hh:118
detail::Joiner< Collection, Separator > join(Collection &&col, Separator &&sep)
Definition: join.hh:60
bool startsWith(string_view total, string_view part)
Definition: StringOp.cc:29
This file implemented 3 utility functions:
Definition: Autofire.cc:9
TclObject makeTclList(Args &&... args)
Definition: TclObject.hh:290
auto remove_if(ForwardRange &&range, UnaryPredicate pred)
Definition: ranges.hh:209
auto find(InputRange &&range, const T &value)
Definition: ranges.hh:130
void sort(RandomAccessRange &&range)
Definition: ranges.hh:34
constexpr auto drop(Range &&range, size_t n)
Definition: view.hh:374
constexpr auto keys(Map &&map)
Definition: view.hh:397
#define OUTER(type, member)
Definition: outer.hh:41
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:134
std::string strCat(Ts &&...ts)
Definition: strCat.hh:591
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:669
constexpr auto xrange(T e)
Definition: xrange.hh:155
constexpr auto begin(const zstring_view &x)
Definition: zstring_view.hh:83
constexpr auto end(const zstring_view &x)
Definition: zstring_view.hh:84