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