From c4203cc8cf1345488ad7205533fd106dad532a06 Mon Sep 17 00:00:00 2001 From: Musa Mahmood Date: Wed, 14 Oct 2026 18:50:34 +0100 Subject: [PATCH] [0.1.19] Install a new hotkey's default when it is added to settings.config hotkeys_missing_lines writes the shipped default of an action the file does not mention, but that save records the hash of what it wrote, so the file was not reloaded and the action stayed unbound until the file next changed. The bindings installed at startup and on reload now include those defaults (hotkeys_with_missing_defaults), as in Ex4 1.47.2; a file of an older version still gets "none". A logger test covers it. --- check_targets.jai | 2 +- first.jai | 2 +- src/test/logger_tests.jai | 32 ++++++++++++++++++++++++++++++++ src/ui/imgui_keyboard.jai | 26 +++++++++++++++++++++++--- src/ui/imgui_theme_registry.jai | 10 +++++----- 5 files changed, 62 insertions(+), 10 deletions(-) diff --git a/check_targets.jai b/check_targets.jai index e416c87..8b4f66b 100644 --- a/check_targets.jai +++ b/check_targets.jai @@ -47,7 +47,7 @@ APP_NAME :: "mcap"; APP_FOLDER_NAME :: "mcap"; GIT_BRANCH :: "check"; GIT_HASH :: "-"; -VERSION :: "0.1.18"; +VERSION :: "0.1.19"; BUILD_DATE :: "-"; DEBUG :: true; COMPILE_TESTS :: true; diff --git a/first.jai b/first.jai index 121cbb9..d5683f0 100644 --- a/first.jai +++ b/first.jai @@ -8,7 +8,7 @@ APP_NAME :: "mcap"; APP_FOLDER_NAME :: "mcap"; APP_BUNDLE_IDENTIFIER :: "com.example.mcap"; -VERSION :: "0.1.18"; +VERSION :: "0.1.19"; // Where everything goes: bin/, or bin-headless/ for builds made with no_crash_dialog, so automated // builds never write over the ones you run by hand. build_directory := "bin"; diff --git a/src/test/logger_tests.jai b/src/test/logger_tests.jai index df5bfe1..8266387 100644 --- a/src/test/logger_tests.jai +++ b/src/test/logger_tests.jai @@ -229,6 +229,38 @@ settings_tests :: () { defer settings_free(*settings); expect(ok && !seen.event_log && settings.event_log_capacity == EVENT_LOG_CAPACITY_DEFAULT, "a file without the key gave seen % and capacity %", seen.event_log, settings.event_log_capacity); + + // A section that does not mention an action gets its default installed right away, since the save + // that writes it into the file does not reload it; a pre-upgrade file gets "none" instead. + { + group("hotkeys missing from [hotkeys]"); + hotkey_actions_init(); + defaults := hotkey_defaults(); + bindings: [..] Hotkey_Binding; + bindings.allocator = temp; + errors := hotkeys_parse_section("logger_tests", "[hotkeys]\nCtrl-Shift-Q program_exit\nnone minimize_to_tray\n", hotkey_actions, *bindings); + expect(errors == 0 && bindings.count == 2, "the partial section gave % bindings and % errors", bindings.count, errors); + + exit_action := hotkey_find_action(hotkey_actions, "program_exit"); + minimize := hotkey_find_action(hotkey_actions, "minimize_to_tray"); + focus_search := hotkey_find_action(hotkey_actions, "focus_search"); + + installed := hotkeys_with_missing_defaults(hotkey_actions, bindings, defaults, APP_USER_SETTINGS_VERSION); + count_for :: (bindings: [] Hotkey_Binding, action: s32) -> int { + count := 0; + for bindings if it.action == action count += 1; + return count; + } + expect(count_for(installed, exit_action) == 1, "program_exit has % bindings, not just its own 1", count_for(installed, exit_action)); + expect(count_for(installed, minimize) == 1, "minimize_to_tray's \"none\" was joined by % more", count_for(installed, minimize) - 1); + expect(count_for(installed, focus_search) == count_for(defaults, focus_search) && count_for(defaults, focus_search) > 0, + "focus_search got % of its % default bindings", count_for(installed, focus_search), count_for(defaults, focus_search)); + missing, _ := hotkeys_missing_lines(hotkey_actions, installed, defaults, APP_USER_SETTINGS_VERSION); + expect(missing == "", "with the defaults added, these are still missing:%", missing); + + old := hotkeys_with_missing_defaults(hotkey_actions, bindings, defaults, APP_USER_SETTINGS_VERSION - 1); + expect(old.count == bindings.count, "a pre-upgrade file got % default bindings", old.count - bindings.count); + } } #scope_export diff --git a/src/ui/imgui_keyboard.jai b/src/ui/imgui_keyboard.jai index b3fc40a..b3aa749 100644 --- a/src/ui/imgui_keyboard.jai +++ b/src/ui/imgui_keyboard.jai @@ -293,9 +293,7 @@ hotkeys_missing_lines :: (actions: [] Hotkey_Action, bindings: [] Hotkey_Binding new_active_count := 0; for action, action_index: actions { - mentioned := false; - for bindings if it.action == action_index { mentioned = true; break; } - if mentioned continue; + if hotkeys_mention_action(bindings, action_index) continue; if !header_written { append(*builder, "\n# Added automatically; new since this file was last saved.\n"); @@ -314,6 +312,28 @@ hotkeys_missing_lines :: (actions: [] Hotkey_Action, bindings: [] Hotkey_Binding return builder_to_string(*builder,, temp), new_active_count; } +// The bindings to install for a section as read: its own, plus the shipped defaults of the actions +// it does not mention, which hotkeys_missing_lines gives the file in the save that follows. That +// save records the hash of what it wrote, so the file is not reloaded, and without these a new +// action would stay unbound until the file next changed. temp. +hotkeys_with_missing_defaults :: (actions: [] Hotkey_Action, bindings: [] Hotkey_Binding, defaults: [] Hotkey_Binding, file_version: s64) -> [] Hotkey_Binding { + if file_version < APP_USER_SETTINGS_VERSION return bindings; // these get "none" + + result: [..] Hotkey_Binding; + result.allocator = temp; + array_add(*result, ..bindings); + for action, action_index: actions { + if hotkeys_mention_action(bindings, action_index) continue; + for defaults if it.action == action_index array_add(*result, it); + } + return result; +} + +hotkeys_mention_action :: (bindings: [] Hotkey_Binding, action_index: int) -> bool { + for bindings if it.action == action_index return true; + return false; +} + // ---------------------------------------------------------------- the [global_hotkeys] section // Registered with the OS, so they fire from any app: RegisterHotKey on Windows, Carbon's diff --git a/src/ui/imgui_theme_registry.jai b/src/ui/imgui_theme_registry.jai index 5044433..7034ac5 100644 --- a/src/ui/imgui_theme_registry.jai +++ b/src/ui/imgui_theme_registry.jai @@ -366,11 +366,11 @@ app_load_user_settings_file :: () -> write_back: bool { bindings: [] Hotkey_Binding = hotkey_defaults(); if ok && seen.hotkeys bindings = app_user_settings.hotkey_bindings; - hotkeys_install(bindings); + hotkeys_install(hotkeys_with_missing_defaults(hotkey_actions, bindings, hotkey_defaults(), version)); global_bindings: [] Hotkey_Binding = global_hotkey_defaults(); if ok && seen.global_hotkeys global_bindings = app_user_settings.global_hotkey_bindings; - global_hotkeys_install(global_bindings); + global_hotkeys_install(hotkeys_with_missing_defaults(global_hotkey_actions, global_bindings, global_hotkey_defaults(), version)); if !ok return false; @@ -804,7 +804,7 @@ app_reload_user_settings_if_changed :: () { settings_file_hash = hash; fresh: App_User_Settings; - ok, seen, _ := settings_parse(path, file_data, *fresh); + ok, seen, version := settings_parse(path, file_data, *fresh); if !ok { settings_free(*fresh); log_error("[settings] Could not apply the changes to %; keeping the current settings.", path); @@ -832,11 +832,11 @@ app_reload_user_settings_if_changed :: () { bindings: [] Hotkey_Binding = hotkey_defaults(); if seen.hotkeys bindings = app_user_settings.hotkey_bindings; - hotkeys_install(bindings); + hotkeys_install(hotkeys_with_missing_defaults(hotkey_actions, bindings, hotkey_defaults(), version)); global_bindings: [] Hotkey_Binding = global_hotkey_defaults(); if seen.global_hotkeys global_bindings = app_user_settings.global_hotkey_bindings; - global_hotkeys_install(global_bindings); + global_hotkeys_install(hotkeys_with_missing_defaults(global_hotkey_actions, global_bindings, global_hotkey_defaults(), version)); global_hotkeys_reapply(); settings_note_synced();