Skip to content

Commit 1fcee14

Browse files
committed
Improve keybinding command error messages for invalid keyspecs
1 parent 74737b4 commit 1fcee14

File tree

2 files changed

+57
-37
lines changed

2 files changed

+57
-37
lines changed

library/include/modules/Hotkey.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ namespace DFHack {
2424
std::string cmdline;
2525
};
2626

27+
DFHACK_EXPORT std::optional<Hotkey::KeySpec> parseKeySpec(std::string spec, std::string* err = nullptr);
2728
DFHACK_EXPORT std::string keyspec_to_string(const KeySpec& spec, bool include_focus=false);
2829
}
2930
class DFHACK_EXPORT HotkeyManager {
@@ -54,7 +55,6 @@ namespace DFHack {
5455
// Returns the latest requested keybind input
5556
std::string readKeybindInput();
5657

57-
std::optional<Hotkey::KeySpec> parseKeySpec(std::string spec);
5858
private:
5959
std::thread hotkey_thread;
6060
std::mutex lock {};

library/modules/Hotkey.cpp

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -68,35 +68,8 @@ std::string Hotkey::keyspec_to_string(const KeySpec &spec, bool include_focus) {
6868
return sym;
6969
}
7070

71-
// Hotkeys actions are executed from an external thread to avoid deadlocks
72-
// that may occur if running commands from the render or simulation threads.
73-
void HotkeyManager::hotkey_thread_fn() {
74-
auto& core = DFHack::Core::getInstance();
75-
76-
std::unique_lock<std::mutex> l(lock);
77-
while (true) {
78-
cond.wait(l, [this]() { return this->hotkey_sig != HotkeySignal::None; });
79-
if (hotkey_sig == HotkeySignal::Shutdown)
80-
return;
81-
if (hotkey_sig != HotkeySignal::CmdReady)
82-
continue;
8371

84-
// Copy and reset important data, then release the lock
85-
this->hotkey_sig = HotkeySignal::None;
86-
std::string cmd = this->queued_command;
87-
this->queued_command.clear();
88-
l.unlock();
89-
90-
// Attempt execution of command
91-
DFHack::color_ostream_proxy out(core.getConsole());
92-
auto res = core.runCommand(out, cmd);
93-
if (res == DFHack::CR_NOT_IMPLEMENTED)
94-
out.printerr("Invalid hotkey command: '%s'\n", cmd.c_str());
95-
l.lock();
96-
}
97-
}
98-
99-
std::optional<KeySpec> HotkeyManager::parseKeySpec(std::string spec) {
72+
std::optional<KeySpec> Hotkey::parseKeySpec(std::string spec, std::string* err) {
10073
KeySpec out;
10174

10275
// Determine focus string, if present
@@ -136,10 +109,42 @@ std::optional<KeySpec> HotkeyManager::parseKeySpec(std::string spec) {
136109
}
137110
}
138111

112+
if (err)
113+
*err = "Unknown key '" + spec + "'";
114+
139115
// Invalid key binding
140116
return std::nullopt;
141117
}
142118

119+
// Hotkeys actions are executed from an external thread to avoid deadlocks
120+
// that may occur if running commands from the render or simulation threads.
121+
void HotkeyManager::hotkey_thread_fn() {
122+
auto& core = DFHack::Core::getInstance();
123+
124+
std::unique_lock<std::mutex> l(lock);
125+
while (true) {
126+
cond.wait(l, [this]() { return this->hotkey_sig != HotkeySignal::None; });
127+
if (hotkey_sig == HotkeySignal::Shutdown)
128+
return;
129+
if (hotkey_sig != HotkeySignal::CmdReady)
130+
continue;
131+
132+
// Copy and reset important data, then release the lock
133+
this->hotkey_sig = HotkeySignal::None;
134+
std::string cmd = this->queued_command;
135+
this->queued_command.clear();
136+
l.unlock();
137+
138+
// Attempt execution of command
139+
DFHack::color_ostream_proxy out(core.getConsole());
140+
auto res = core.runCommand(out, cmd);
141+
if (res == DFHack::CR_NOT_IMPLEMENTED)
142+
out.printerr("Invalid hotkey command: '%s'\n", cmd.c_str());
143+
l.lock();
144+
}
145+
}
146+
147+
143148
bool HotkeyManager::addKeybind(KeySpec spec, std::string cmd) {
144149
// No point in a hotkey with no action
145150
if (cmd.empty())
@@ -164,7 +169,7 @@ bool HotkeyManager::addKeybind(KeySpec spec, std::string cmd) {
164169
}
165170

166171
bool HotkeyManager::addKeybind(std::string keyspec, std::string cmd) {
167-
std::optional<KeySpec> spec_opt = parseKeySpec(keyspec);
172+
std::optional<KeySpec> spec_opt = Hotkey::parseKeySpec(keyspec);
168173
if (!spec_opt.has_value())
169174
return false;
170175
return this->addKeybind(spec_opt.value(), cmd);
@@ -189,7 +194,7 @@ bool HotkeyManager::clearKeybind(const KeySpec& spec, bool any_focus, std::strin
189194
}
190195

191196
bool HotkeyManager::clearKeybind(std::string keyspec, bool any_focus, std::string cmdline) {
192-
std::optional<KeySpec> spec_opt = parseKeySpec(keyspec);
197+
std::optional<KeySpec> spec_opt = Hotkey::parseKeySpec(keyspec);
193198
if (!spec_opt.has_value())
194199
return false;
195200
return this->clearKeybind(spec_opt.value(), any_focus, cmdline);
@@ -226,7 +231,7 @@ std::vector<std::string> HotkeyManager::listKeybinds(const KeySpec& spec) {
226231
}
227232

228233
std::vector<std::string> HotkeyManager::listKeybinds(std::string keyspec) {
229-
std::optional<KeySpec> spec_opt = parseKeySpec(keyspec);
234+
std::optional<KeySpec> spec_opt = Hotkey::parseKeySpec(keyspec);
230235
if (!spec_opt.has_value())
231236
return {};
232237
return this->listKeybinds(spec_opt.value());
@@ -352,27 +357,42 @@ std::string HotkeyManager::readKeybindInput() {
352357
}
353358

354359
void HotkeyManager::handleKeybindingCommand(color_ostream &con, const std::vector<std::string>& parts) {
360+
std::string parse_error;
355361
if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) {
356362
std::string keystr = parts[1];
357363
if (parts[0] == "set")
358364
clearKeybind(keystr);
359365
for (const auto& part : parts | std::views::drop(2) | std::views::reverse) {
360-
if (!addKeybind(keystr, part)) {
361-
con.printerr("Invalid key spec: %s\n", keystr.c_str());
366+
auto spec = Hotkey::parseKeySpec(keystr, &parse_error);
367+
if (!spec.has_value()) {
368+
con.printerr("%s\n", parse_error.c_str());
369+
break;
370+
}
371+
if (!addKeybind(spec.value(), part)) {
372+
con.printerr("Invalid command: '%s'\n", part.c_str());
362373
break;
363374
}
364375
}
365376
}
366377
else if (parts.size() >= 2 && parts[0] == "clear") {
367378
for (const auto& part : parts | std::views::drop(1)) {
368-
if (!clearKeybind(part)) {
369-
con.printerr("Invalid key spec: %s\n", part.c_str());
379+
auto spec = Hotkey::parseKeySpec(part, &parse_error);
380+
if (!spec.has_value()) {
381+
con.printerr("%s\n", parse_error.c_str());
382+
}
383+
if (!clearKeybind(spec.value())) {
384+
con.printerr("No matching keybinds to remove\n");
370385
break;
371386
}
372387
}
373388
}
374389
else if (parts.size() == 2 && parts[0] == "list") {
375-
std::vector<std::string> list = listKeybinds(parts[1]);
390+
auto spec = Hotkey::parseKeySpec(parts[1], &parse_error);
391+
if (!spec.has_value()) {
392+
con.printerr("%s\n", parse_error.c_str());
393+
return;
394+
}
395+
std::vector<std::string> list = listKeybinds(spec.value());
376396
if (list.empty())
377397
con << "No bindings." << std::endl;
378398
for (const auto& kb : list)

0 commit comments

Comments
 (0)