9#include "absl/strings/str_format.h"
24 const std::string& category,
25 const std::string& description,
26 const std::string& shortcut,
27 std::function<
void()> callback) {
32 entry.shortcut = shortcut;
33 entry.callback = callback;
45 std::unique_ptr<CommandProvider> provider) {
48 const std::string
id = provider->ProviderId();
51 "Provider with empty id refused; skipping registration");
57 if ((*it)->ProviderId() == id) {
58 LOG_WARN(
"CommandPalette",
"Replacing existing CommandProvider '%s'",
65 CommandProvider* raw = provider.get();
76 [&](
const std::unique_ptr<CommandProvider>& p) {
77 return p && p->ProviderId() == provider_id;
84 if (provider && provider->ProviderId() == provider_id) {
87 provider->Provide(
this);
96 std::vector<std::string> ids;
100 ids.push_back(provider->ProviderId());
102 for (
const auto&
id : ids) {
108 if (provider_id.empty())
111 if (it->second.provider_id == provider_id) {
122 it->second.usage_count++;
123 it->second.last_used_ms =
124 std::chrono::duration_cast<std::chrono::milliseconds>(
125 std::chrono::system_clock::now().time_since_epoch())
131 const std::string& query) {
137 size_t query_idx = 0;
139 std::string text_lower = text;
140 std::string query_lower = query;
141 std::transform(text_lower.begin(), text_lower.end(), text_lower.begin(),
143 std::transform(query_lower.begin(), query_lower.end(), query_lower.begin(),
147 if (text_lower == query_lower)
151 if (text_lower.find(query_lower) == 0)
155 if (text_lower.find(query_lower) != std::string::npos)
159 while (text_idx < text_lower.length() && query_idx < query_lower.length()) {
160 if (text_lower[text_idx] == query_lower[query_idx]) {
168 if (query_idx != query_lower.length())
175 const std::string& query) {
176 std::vector<std::pair<int, CommandEntry>> scored;
178 for (
const auto& [name, entry] :
commands_) {
182 score +=
FuzzyScore(entry.category, query) / 2;
183 score +=
FuzzyScore(entry.description, query) / 4;
186 score += entry.usage_count * 2;
188 auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
189 std::chrono::system_clock::now().time_since_epoch())
191 int64_t age_ms = now_ms - entry.last_used_ms;
192 if (age_ms < 60000) {
194 }
else if (age_ms < 3600000) {
199 scored.push_back({score, entry});
204 std::sort(scored.begin(), scored.end(),
205 [](
const auto& a,
const auto& b) { return a.first > b.first; });
207 std::vector<CommandEntry> results;
208 for (
const auto& [score, entry] : scored) {
209 results.push_back(entry);
216 std::vector<CommandEntry> recent;
218 for (
const auto& [name, entry] :
commands_) {
219 if (entry.usage_count > 0) {
220 recent.push_back(entry);
224 std::sort(recent.begin(), recent.end(),
225 [](
const CommandEntry& a,
const CommandEntry& b) {
226 return a.last_used_ms > b.last_used_ms;
229 if (recent.size() >
static_cast<size_t>(limit)) {
230 recent.resize(limit);
237 std::vector<CommandEntry> frequent;
239 for (
const auto& [name, entry] :
commands_) {
240 if (entry.usage_count > 0) {
241 frequent.push_back(entry);
245 std::sort(frequent.begin(), frequent.end(),
246 [](
const CommandEntry& a,
const CommandEntry& b) {
247 return a.usage_count > b.usage_count;
250 if (frequent.size() >
static_cast<size_t>(limit)) {
251 frequent.resize(limit);
263 for (
const auto& [name, entry] :
commands_) {
264 if (entry.usage_count > 0) {
266 cmd[
"usage_count"] = entry.usage_count;
267 cmd[
"last_used_ms"] = entry.last_used_ms;
268 j[
"commands"][
name] = cmd;
272 std::ofstream file(filepath);
273 if (file.is_open()) {
275 LOG_INFO(
"CommandPalette",
"Saved command history to %s",
278 }
catch (
const std::exception& e) {
279 LOG_ERROR(
"CommandPalette",
"Failed to save command history: %s", e.what());
284 if (!std::filesystem::exists(filepath)) {
289 std::ifstream file(filepath);
290 if (!file.is_open()) {
294 std::string content((std::istreambuf_iterator<char>(file)),
295 std::istreambuf_iterator<char>());
298 if (!j.
contains(
"commands") || !j[
"commands"].is_object()) {
303 for (
auto& [name, cmd_json] : j[
"commands"].items()) {
306 it->second.usage_count = cmd_json.value(
"usage_count", 0);
307 it->second.last_used_ms = cmd_json.value(
"last_used_ms", int64_t{0});
312 LOG_INFO(
"CommandPalette",
"Loaded %d command history entries from %s",
313 loaded, filepath.c_str());
314 }
catch (
const std::exception& e) {
315 LOG_ERROR(
"CommandPalette",
"Failed to load command history: %s", e.what());
320 std::vector<CommandEntry> result;
322 for (
const auto& [name, entry] :
commands_) {
323 result.push_back(entry);
329 WorkspaceWindowManager* window_manager,
size_t session_id) {
333 for (
const auto& base_id : window_manager->GetWindowsInSession(session_id)) {
334 const auto* descriptor =
335 window_manager->GetWindowDescriptor(session_id, base_id);
341 std::string show_name =
342 absl::StrFormat(
"Show: %s", descriptor->display_name);
343 std::string show_desc =
344 absl::StrFormat(
"Open the %s window", descriptor->display_name);
347 descriptor->shortcut_hint,
348 [window_manager, base_id, session_id]() {
349 window_manager->OpenWindow(session_id, base_id);
353 std::string hide_name =
354 absl::StrFormat(
"Hide: %s", descriptor->display_name);
355 std::string hide_desc =
356 absl::StrFormat(
"Close the %s window", descriptor->display_name);
359 [window_manager, base_id, session_id]() {
360 window_manager->CloseWindow(session_id, base_id);
364 std::string toggle_name =
365 absl::StrFormat(
"Toggle: %s", descriptor->display_name);
366 std::string toggle_desc = absl::StrFormat(
"Toggle the %s window visibility",
367 descriptor->display_name);
370 [window_manager, base_id, session_id]() {
371 window_manager->ToggleWindow(session_id, base_id);
377 std::string pin_toggle_name =
378 absl::StrFormat(
"Toggle Pin: %s", descriptor->display_name);
379 std::string pin_toggle_desc =
380 absl::StrFormat(
"Keep the %s window visible across editor switches",
381 descriptor->display_name);
384 [window_manager, base_id, session_id]() {
386 window_manager->IsWindowPinned(session_id, base_id);
387 window_manager->SetWindowPinned(session_id, base_id, !pinned);
393 std::function<
void(
const std::string&)> switch_callback) {
397 for (
const auto& category : categories) {
398 std::string
name = absl::StrFormat(
"Switch to: %s Editor", category);
400 absl::StrFormat(
"Switch to the %s editor category", category);
403 [switch_callback, category]() { switch_callback(category); });
408 std::function<
void(
const std::string&)> apply_callback) {
415 static const ProfileInfo profiles[] = {
416 {
"code",
"Code",
"Focused editing workspace with minimal panel noise"},
418 "Debugger-first workspace for tracing and memory tools"},
419 {
"mapping",
"Mapping",
420 "Map-centric workspace for overworld/dungeon flows"},
421 {
"chat",
"Chat + Agent",
422 "Agent collaboration workspace with chat-centric layout"},
425 for (
const auto& profile : profiles) {
426 std::string
name = absl::StrFormat(
"Apply Profile: %s", profile.name);
428 [apply_callback, profile_id = std::string(profile.id)]() {
429 apply_callback(
"profile:" + profile_id);
434 "Capture current layout as temporary session snapshot",
"",
435 [apply_callback]() { apply_callback(
"session:capture"); });
437 "Restore temporary session snapshot",
"",
438 [apply_callback]() { apply_callback(
"session:restore"); });
440 "Clear temporary session snapshot",
"",
441 [apply_callback]() { apply_callback(
"session:clear"); });
449 static const PresetInfo presets[] = {
450 {
"Minimal",
"Minimal workspace with essential panels only"},
451 {
"Developer",
"Debug-focused layout with emulator and memory tools"},
452 {
"Designer",
"Visual-focused layout for graphics and palette editing"},
453 {
"Modder",
"Full-featured layout with all panels available"},
454 {
"Overworld Expert",
"Optimized layout for overworld editing"},
455 {
"Dungeon Expert",
"Optimized layout for dungeon editing"},
456 {
"Testing",
"QA-focused layout with testing tools"},
457 {
"Audio",
"Music and sound editing focused layout"},
460 for (
const auto& preset : presets) {
461 std::string
name = absl::StrFormat(
"Apply Layout: %s", preset.name);
464 [apply_callback, preset_name = std::string(preset.name)]() {
465 apply_callback(preset_name);
471 "Reset to the default layout for current editor",
"",
472 [apply_callback]() { apply_callback(
"Default"); });
476 std::function<
void(
const std::string&)> open_callback) {
477 const auto& recent_files =
480 for (
const auto& filepath : recent_files) {
482 if (!std::filesystem::exists(filepath)) {
487 std::filesystem::path path(filepath);
488 std::string filename = path.filename().string();
490 std::string
name = absl::StrFormat(
"Open Recent: %s", filename);
491 std::string desc = absl::StrFormat(
"Open file %s", filepath);
494 [open_callback, filepath]() { open_callback(filepath); });
499 constexpr int kTotalRooms = 0x128;
500 for (
int room_id = 0; room_id < kTotalRooms; ++room_id) {
502 const std::string room_name =
503 label.empty() ? absl::StrFormat(
"Room %03X", room_id) : label;
505 const std::string
name =
506 absl::StrFormat(
"Dungeon: Open Room [%03X] %s", room_id, room_name);
507 const std::string desc =
508 absl::StrFormat(
"Jump to dungeon room %03X", room_id);
520 WorkspaceWindowManager* window_manager,
size_t session_id) {
521 if (window_manager) {
522 const auto categories = window_manager->GetAllCategories(session_id);
523 for (
const auto& category : categories) {
524 for (
const auto& descriptor :
525 window_manager->GetWindowsInCategory(session_id,
category)) {
526 if (descriptor.workflow_group.empty()) {
529 if (descriptor.enabled_condition && !descriptor.enabled_condition()) {
532 const std::string label = descriptor.workflow_label.empty()
533 ? descriptor.display_name
534 : descriptor.workflow_label;
535 const std::string group = descriptor.workflow_group.empty()
536 ? std::string(
"General")
537 : descriptor.workflow_group;
539 descriptor.workflow_description.empty()
540 ? absl::StrFormat(
"Open %s", descriptor.display_name)
541 : descriptor.workflow_description;
544 description, descriptor.shortcut_hint,
545 [window_manager, session_id, panel_id = descriptor.card_id]() {
546 window_manager->OpenWindow(session_id, panel_id);
552 for (
const auto& action : ContentRegistry::WorkflowActions::
GetAll()) {
553 if (action.enabled && !action.enabled()) {
556 const std::string group =
557 action.group.empty() ? std::string(
"General") : action.group;
558 AddCommand(absl::StrFormat(
"%s: %s", group, action.label),
565 const RecentProjectsModel* model,
566 const std::vector<std::string>& template_names,
567 std::function<
void(
const std::string&)> remove_callback,
568 std::function<
void(
const std::string&)> toggle_pin_callback,
569 std::function<
void()> undo_remove_callback,
570 std::function<
void()> clear_recents_callback,
571 std::function<
void(
const std::string&)> create_from_template_callback,
572 std::function<
void()> dismiss_welcome_callback,
573 std::function<
void()> show_welcome_callback) {
578 for (
const auto& entry : model->entries()) {
579 if (entry.unavailable)
581 const std::string label = entry.display_name_override.empty()
583 : entry.display_name_override;
584 const std::string path = entry.filepath;
586 if (remove_callback) {
587 const std::string
name =
588 absl::StrFormat(
"Welcome: Remove Recent \"%s\"", label);
589 const std::string desc =
590 absl::StrFormat(
"Remove %s from the welcome screen's recents list.",
593 [remove_callback, path]() { remove_callback(path); });
595 if (toggle_pin_callback) {
596 const std::string
name = absl::StrFormat(
597 "Welcome: %s Recent \"%s\"", entry.pinned ?
"Unpin" :
"Pin", label);
598 const std::string desc =
599 absl::StrFormat(
"%s %s on the welcome screen.",
600 entry.pinned ?
"Unpin" :
"Pin", entry.filepath);
603 [toggle_pin_callback, path]() { toggle_pin_callback(path); });
608 if (clear_recents_callback) {
610 "Forget every entry in the welcome screen's recent list.",
"",
611 clear_recents_callback);
614 if (undo_remove_callback) {
616 "Restore the last recent-project entry removed via Forget.",
"",
617 undo_remove_callback);
620 if (create_from_template_callback) {
621 for (
const auto& template_name : template_names) {
622 if (template_name.empty())
624 const std::string
name = absl::StrFormat(
625 "Welcome: Create Project from Template: %s", template_name);
626 const std::string desc = absl::StrFormat(
627 "Start a new project using the \"%s\" template.", template_name);
629 [create_from_template_callback, template_name]() {
630 create_from_template_callback(template_name);
635 if (show_welcome_callback) {
637 "Bring back the welcome screen if it's been dismissed.",
"",
638 show_welcome_callback);
640 if (dismiss_welcome_callback) {
642 "Hide the welcome screen for the rest of this session.",
"",
643 dismiss_welcome_callback);
static Json parse(const std::string &)
std::string dump(int=-1, char=' ', bool=false, int=0) const
bool contains(const std::string &) const
std::vector< CommandEntry > SearchCommands(const std::string &query)
void RegisterDungeonRoomCommands(size_t session_id)
Register dungeon room navigation commands.
void RegisterWelcomeCommands(const RecentProjectsModel *model, const std::vector< std::string > &template_names, std::function< void(const std::string &)> remove_callback, std::function< void(const std::string &)> toggle_pin_callback, std::function< void()> undo_remove_callback, std::function< void()> clear_recents_callback, std::function< void(const std::string &)> create_from_template_callback, std::function< void()> dismiss_welcome_callback, std::function< void()> show_welcome_callback)
Expose welcome-screen actions through the command palette.
void SaveHistory(const std::string &filepath)
Save command usage history to disk.
void Clear()
Clear all commands (and forget every registered provider).
void RegisterLayoutCommands(std::function< void(const std::string &)> apply_callback)
Register layout preset commands.
void AddCommand(const std::string &name, const std::string &category, const std::string &description, const std::string &shortcut, std::function< void()> callback)
void LoadHistory(const std::string &filepath)
Load command usage history from disk.
void UnregisterProvider(const std::string &provider_id)
void RegisterPanelCommands(WorkspaceWindowManager *window_manager, size_t session_id)
Register all window toggle commands from WorkspaceWindowManager.
void RemoveProviderCommands(const std::string &provider_id)
std::vector< CommandEntry > GetAllCommands() const
Get all registered commands.
void RecordUsage(const std::string &name)
std::unordered_map< std::string, CommandEntry > commands_
void RegisterProvider(std::unique_ptr< CommandProvider > provider)
void RegisterWorkflowCommands(WorkspaceWindowManager *window_manager, size_t session_id)
Register hack workflow commands from workflow-aware panels/actions.
void RegisterEditorCommands(std::function< void(const std::string &)> switch_callback)
Register all editor switch commands.
std::vector< CommandEntry > GetRecentCommands(int limit=10)
void RefreshProviders()
Remove-and-re-run every registered provider.
std::string current_provider_id_
std::vector< std::unique_ptr< CommandProvider > > providers_
void RegisterRecentFilesCommands(std::function< void(const std::string &)> open_callback)
Register commands to open recent files.
std::vector< CommandEntry > GetFrequentCommands(int limit=10)
static int FuzzyScore(const std::string &text, const std::string &query)
void RefreshProvider(const std::string &provider_id)
static std::vector< std::string > GetAllEditorCategories()
Get all editor categories in display order for sidebar.
static RecentFilesManager & GetInstance()
const std::vector< std::string > & GetRecentFiles() const
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
::yaze::EventBus * event_bus()
Get the current EventBus instance.
std::vector< WindowContent * > GetAll()
Get all registered panels.
std::string GetRoomLabel(int id)
Convenience function to get a room label.
static constexpr const char * kLayout
static constexpr const char * kWorkflow
static constexpr const char * kFile
static constexpr const char * kView
static constexpr const char * kEditor
static constexpr const char * kPanel
static constexpr const char * kNavigation
static JumpToRoomRequestEvent Create(int room, size_t session=0)