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