From fcf439dfa02d27d83f9217f51f9a69b1e24acdd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Wed, 27 May 2026 20:47:47 +0200 Subject: [PATCH] core: track per-contributor overrides in theme registry Refactor the theme registry to store one sub-table per contributor instead of a single merged hashtable. Each registered theme now holds a linked list of t_theme_contribution entries: struct t_theme_contribution { struct t_weechat_plugin *plugin; /* NULL = core */ const void *script; /* NULL for non-script */ struct t_hashtable *overrides; ... }; Identity of a contributor is the (plugin, script) pair: - (NULL, NULL) -> core (theme_builtin_init) - (plugin, NULL) -> plugin-level contribution - (plugin, script) -> individual script (filled in by next commit) theme_register is now (plugin, script, name, overrides). It searches the existing contributions for a matching (plugin, script) and merges the new overrides into it; otherwise it appends a fresh contribution. The public macro weechat_theme_register(name, overrides) still takes two args - it now expands to pass weechat_plugin and NULL for script. theme_apply iterates contributions in list order, calling config_file_option_set for each entry; later contributions naturally win for duplicate keys. Two new internal helpers prepare for the lifecycle work in the next two commits: - theme_unregister_plugin (plugin): drops every contribution owned by that plugin (with script == NULL). - theme_unregister_script (plugin, script): drops every contribution owned by that script. Neither is called yet; the auto-purge wiring lands in commits 24 (plugin_unloaded signal) and 25 (script API + script-unload hook). Other touched code: - core-theme-builtin.c switches to theme_register (NULL, NULL, ...). - core-command.c /theme info uses theme_overrides_count helper instead of reaching into theme->overrides (which no longer exists). - WEECHAT_PLUGIN_API_VERSION bumped to 20260527-01 (function-pointer signature change). Two new tests cover the new semantics: - UnregisterByOwner: registers four contributions from distinct (plugin, script) pairs, then prunes by plugin and by script, asserting per-contribution removal. - RegisterMergesPerContributor: two successive register calls from the same (plugin, script) merge into a single contribution with later keys overriding earlier ones. Existing tests are updated to use the new theme_register signature, theme_overrides_count, and theme_get_override (replacing direct access to theme->overrides->items_count and hashtable_get on theme->overrides). No plugin or script call sites change - the public weechat_theme_register macro keeps the same shape. --- src/core/core-command.c | 3 +- src/core/core-theme-builtin.c | 2 +- src/core/core-theme.c | 301 +++++++++++++++++++++++++--- src/core/core-theme.h | 39 +++- src/plugins/weechat-plugin.h | 7 +- tests/unit/core/test-core-theme.cpp | 167 +++++++++++---- 6 files changed, 441 insertions(+), 78 deletions(-) diff --git a/src/core/core-command.c b/src/core/core-command.c index 24c7879ec..b60ee5afb 100644 --- a/src/core/core-command.c +++ b/src/core/core-command.c @@ -7406,8 +7406,7 @@ COMMAND_CALLBACK(theme) ? ptr_theme->weechat_version : ""); gui_chat_printf (NULL, _(" overrides : %d"), - (ptr_theme->overrides) - ? ptr_theme->overrides->items_count : 0); + theme_overrides_count (ptr_theme)); free (path); theme_free (file_theme); return WEECHAT_RC_OK; diff --git a/src/core/core-theme-builtin.c b/src/core/core-theme-builtin.c index 5293c996f..fe82a7385 100644 --- a/src/core/core-theme-builtin.c +++ b/src/core/core-theme-builtin.c @@ -111,7 +111,7 @@ theme_builtin_register_entries (const char *name, for (i = 0; entries[i].option; i++) hashtable_set (overrides, entries[i].option, entries[i].value); - theme_register (name, overrides); + theme_register (NULL, NULL, name, overrides); hashtable_free (overrides); } diff --git a/src/core/core-theme.c b/src/core/core-theme.c index c7e5e69ba..b5180d9b7 100644 --- a/src/core/core-theme.c +++ b/src/core/core-theme.c @@ -116,25 +116,32 @@ theme_alloc (const char *name) new_theme->description = strdup (""); new_theme->date = theme_format_now (); new_theme->weechat_version = strdup (version_get_version ()); - new_theme->overrides = hashtable_new (32, - WEECHAT_HASHTABLE_STRING, - WEECHAT_HASHTABLE_STRING, - NULL, NULL); if (!new_theme->name || !new_theme->description - || !new_theme->date || !new_theme->weechat_version - || !new_theme->overrides) + || !new_theme->date || !new_theme->weechat_version) { free (new_theme->name); free (new_theme->description); free (new_theme->date); free (new_theme->weechat_version); - hashtable_free (new_theme->overrides); free (new_theme); return NULL; } return new_theme; } +/* + * Frees one contribution (does not unlink from the parent theme list). + */ + +void +theme_contribution_free (struct t_theme_contribution *contribution) +{ + if (!contribution) + return; + hashtable_free (contribution->overrides); + free (contribution); +} + /* * Frees a theme (does not unlink from registry; caller handles that). */ @@ -142,13 +149,21 @@ theme_alloc (const char *name) void theme_free (struct t_theme *theme) { + struct t_theme_contribution *ptr_contribution, *next_contribution; + if (!theme) return; + ptr_contribution = theme->contributions; + while (ptr_contribution) + { + next_contribution = ptr_contribution->next_contribution; + theme_contribution_free (ptr_contribution); + ptr_contribution = next_contribution; + } free (theme->name); free (theme->description); free (theme->date); free (theme->weechat_version); - hashtable_free (theme->overrides); free (theme); } @@ -171,23 +186,93 @@ theme_merge_overrides_cb (void *data, } /* - * Registers a theme by name with a set of option overrides. + * Searches the contribution belonging to the given (plugin, script) pair + * in a theme's contribution list. Returns NULL if not found. + */ + +struct t_theme_contribution * +theme_search_contribution (struct t_theme *theme, + struct t_weechat_plugin *plugin, + const void *script) +{ + struct t_theme_contribution *ptr; + + if (!theme) + return NULL; + for (ptr = theme->contributions; ptr; ptr = ptr->next_contribution) + { + if ((ptr->plugin == plugin) && (ptr->script == script)) + return ptr; + } + return NULL; +} + +/* + * Allocates a new contribution and appends it to a theme's list. * - * If a theme with the given name already exists, the provided overrides - * are merged into the existing theme's hashtable (later registrations - * override earlier ones for duplicate keys). This lets plugins/scripts - * register their per-theme contributions without coordinating with core. + * Returns the new contribution, NULL on error. + */ + +struct t_theme_contribution * +theme_contribution_new (struct t_theme *theme, + struct t_weechat_plugin *plugin, + const void *script) +{ + struct t_theme_contribution *new_contribution; + + new_contribution = calloc (1, sizeof (*new_contribution)); + if (!new_contribution) + return NULL; + new_contribution->plugin = plugin; + new_contribution->script = script; + new_contribution->overrides = hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, NULL); + if (!new_contribution->overrides) + { + free (new_contribution); + return NULL; + } + new_contribution->prev_contribution = theme->last_contribution; + new_contribution->next_contribution = NULL; + if (theme->last_contribution) + theme->last_contribution->next_contribution = new_contribution; + else + theme->contributions = new_contribution; + theme->last_contribution = new_contribution; + return new_contribution; +} + +/* + * Registers a contribution to a theme. * - * The "overrides" hashtable passed in is read-only from this function's - * perspective; the caller retains ownership and may free it. + * Identity is the (plugin, script) pair: + * - plugin == NULL && script == NULL => core + * - plugin != NULL && script == NULL => plugin-level + * - plugin != NULL && script != NULL => individual script * - * Returns pointer to theme (existing or newly created), NULL on error. + * If a contribution for the same (plugin, script) already exists under + * the named theme, the new overrides are merged into it (later keys + * win). Otherwise a new contribution is appended. Across distinct + * contributors, contributions are applied in list order at apply-time + * (later contributions override earlier ones for duplicate keys). + * + * The "overrides" hashtable passed in is read-only here; the caller + * retains ownership and may free it after the call. + * + * Returns pointer to the theme (existing or newly created), NULL on + * error. */ struct t_theme * -theme_register (const char *name, struct t_hashtable *overrides) +theme_register (struct t_weechat_plugin *plugin, + const void *script, + const char *name, + struct t_hashtable *overrides) { struct t_theme *theme; + struct t_theme_contribution *contribution; if (!name || !name[0]) return NULL; @@ -209,14 +294,158 @@ theme_register (const char *name, struct t_hashtable *overrides) if (overrides) { + contribution = theme_search_contribution (theme, plugin, script); + if (!contribution) + { + contribution = theme_contribution_new (theme, plugin, script); + if (!contribution) + return theme; /* theme exists but contribution failed */ + } hashtable_map (overrides, &theme_merge_overrides_cb, - theme->overrides); + contribution->overrides); } return theme; } +/* + * Drops one contribution from a theme (unlinks and frees it). + */ + +void +theme_drop_contribution (struct t_theme *theme, + struct t_theme_contribution *contribution) +{ + if (!theme || !contribution) + return; + if (contribution->prev_contribution) + { + contribution->prev_contribution->next_contribution = + contribution->next_contribution; + } + else + { + theme->contributions = contribution->next_contribution; + } + if (contribution->next_contribution) + { + contribution->next_contribution->prev_contribution = + contribution->prev_contribution; + } + else + { + theme->last_contribution = contribution->prev_contribution; + } + theme_contribution_free (contribution); +} + +/* + * Drops all contributions owned by a given plugin (across every theme). + * Called from the plugin-unload lifecycle (next commit). + * + * Contributions whose script is non-NULL are kept (they belong to + * individual scripts and are cleaned up separately on script unload). + */ + +void +theme_unregister_plugin (struct t_weechat_plugin *plugin) +{ + struct t_theme *ptr_theme; + struct t_theme_contribution *ptr_contribution, *next_contribution; + + if (!plugin) + return; + for (ptr_theme = themes; ptr_theme; ptr_theme = ptr_theme->next_theme) + { + ptr_contribution = ptr_theme->contributions; + while (ptr_contribution) + { + next_contribution = ptr_contribution->next_contribution; + if ((ptr_contribution->plugin == plugin) + && (ptr_contribution->script == NULL)) + { + theme_drop_contribution (ptr_theme, ptr_contribution); + } + ptr_contribution = next_contribution; + } + } +} + +/* + * Drops all contributions owned by a given script (across every theme). + * Called from the script-unload lifecycle (a later commit). + */ + +void +theme_unregister_script (struct t_weechat_plugin *plugin, + const void *script) +{ + struct t_theme *ptr_theme; + struct t_theme_contribution *ptr_contribution, *next_contribution; + + if (!plugin || !script) + return; + for (ptr_theme = themes; ptr_theme; ptr_theme = ptr_theme->next_theme) + { + ptr_contribution = ptr_theme->contributions; + while (ptr_contribution) + { + next_contribution = ptr_contribution->next_contribution; + if ((ptr_contribution->plugin == plugin) + && (ptr_contribution->script == script)) + { + theme_drop_contribution (ptr_theme, ptr_contribution); + } + ptr_contribution = next_contribution; + } + } +} + +/* + * Returns the total number of overrides across all contributions of a + * theme. Duplicate keys (across contributions) are counted multiple + * times; the actual merged-unique count is at most this number. + */ + +int +theme_overrides_count (struct t_theme *theme) +{ + struct t_theme_contribution *ptr; + int n; + + if (!theme) + return 0; + n = 0; + for (ptr = theme->contributions; ptr; ptr = ptr->next_contribution) + n += ptr->overrides->items_count; + return n; +} + +/* + * Returns the effective value of an option override across the theme's + * contributions (later contributions win). Returns NULL if no + * contribution provides the key. + */ + +const char * +theme_get_override (struct t_theme *theme, const char *option_name) +{ + struct t_theme_contribution *ptr; + const char *value, *latest; + + if (!theme || !option_name) + return NULL; + latest = NULL; + for (ptr = theme->contributions; ptr; ptr = ptr->next_contribution) + { + value = (const char *)hashtable_get (ptr->overrides, option_name); + if (value) + latest = value; + } + return latest; +} + /* * Compares two themes by name (callback used by arraylist sort). * @@ -500,6 +729,7 @@ theme_file_parse (const char *path) char line[8192], *ptr, *end, *eq, *key, *value; int line_number, in_options; struct t_theme *theme; + struct t_theme_contribution *contribution; if (!path) return NULL; @@ -514,6 +744,15 @@ theme_file_parse (const char *path) fclose (file); return NULL; } + /* file themes carry a single anonymous (plugin=NULL, script=NULL) + contribution holding everything in the [options] section */ + contribution = theme_contribution_new (theme, NULL, NULL); + if (!contribution) + { + theme_free (theme); + fclose (file); + return NULL; + } /* clear the placeholder name; the file should provide it */ free (theme->name); theme->name = NULL; @@ -617,7 +856,7 @@ theme_file_parse (const char *path) if (in_options) { - hashtable_set (theme->overrides, key, value); + hashtable_set (contribution->overrides, key, value); } else { @@ -685,8 +924,8 @@ int theme_apply (const char *name) { struct t_theme *file_theme = NULL; - struct t_theme *registry_theme = NULL; - struct t_hashtable *overrides = NULL; + struct t_theme *theme = NULL; + struct t_theme_contribution *ptr_contribution; char *path = NULL; char *backup_name = NULL; @@ -709,12 +948,12 @@ theme_apply (const char *name) free (path); return WEECHAT_RC_ERROR; } - overrides = file_theme->overrides; + theme = file_theme; } else { - registry_theme = theme_search (name); - if (!registry_theme) + theme = theme_search (name); + if (!theme) { gui_chat_printf (NULL, _("%sTheme \"%s\" not found"), @@ -723,7 +962,6 @@ theme_apply (const char *name) free (path); return WEECHAT_RC_ERROR; } - overrides = registry_theme->overrides; } free (path); @@ -744,10 +982,17 @@ theme_apply (const char *name) } } - /* apply each override; per-option refreshes are suppressed via the - theme_applying flag (see config_change_color) */ + /* Apply each contribution in order; per-option refreshes are + suppressed via the theme_applying flag (see config_change_color). + Later contributions naturally win for duplicate keys because + config_file_option_set is called for each in sequence. */ theme_applying = 1; - hashtable_map (overrides, &theme_apply_set_option_cb, NULL); + for (ptr_contribution = theme->contributions; ptr_contribution; + ptr_contribution = ptr_contribution->next_contribution) + { + hashtable_map (ptr_contribution->overrides, + &theme_apply_set_option_cb, NULL); + } theme_applying = 0; /* file_theme (if any) is transient: discard now */ diff --git a/src/core/core-theme.h b/src/core/core-theme.h index 05e0e7e01..2f51848c8 100644 --- a/src/core/core-theme.h +++ b/src/core/core-theme.h @@ -24,16 +24,35 @@ struct t_hashtable; struct t_arraylist; +struct t_weechat_plugin; + +/* + * A contribution is one (owner, overrides) pair attached to a theme. + * "owner" is identified by a (plugin, script) pair: + * - plugin == NULL && script == NULL => core + * - plugin != NULL && script == NULL => plugin (e.g. irc, fset) + * - plugin != NULL && script != NULL => individual script under that + * script-language plugin + */ +struct t_theme_contribution +{ + struct t_weechat_plugin *plugin; + const void *script; + struct t_hashtable *overrides; /* full_option_name -> value */ + struct t_theme_contribution *prev_contribution; + struct t_theme_contribution *next_contribution; +}; struct t_theme { - char *name; /* "dark", "solarized", ... */ + char *name; /* "light", "solarized", ... */ char *description; /* free-form text */ char *date; /* "YYYY-MM-DD HH:MM:SS" */ char *weechat_version; /* version at registration time */ - struct t_hashtable *overrides; /* full_option_name -> value */ - struct t_theme *prev_theme; /* link to previous theme */ - struct t_theme *next_theme; /* link to next theme */ + struct t_theme_contribution *contributions; + struct t_theme_contribution *last_contribution; + struct t_theme *prev_theme; + struct t_theme *next_theme; }; extern struct t_theme *themes; @@ -41,8 +60,13 @@ extern struct t_theme *last_theme; extern int theme_applying; /* gate for config_change_color */ extern struct t_theme *theme_search (const char *name); -extern struct t_theme *theme_register (const char *name, +extern struct t_theme *theme_register (struct t_weechat_plugin *plugin, + const void *script, + const char *name, struct t_hashtable *overrides); +extern int theme_overrides_count (struct t_theme *theme); +extern const char *theme_get_override (struct t_theme *theme, + const char *option_name); extern struct t_arraylist *theme_list (void); extern int theme_apply (const char *name); extern int theme_save (const char *name, int full); @@ -52,6 +76,11 @@ extern char *theme_user_file_path (const char *name); extern struct t_theme *theme_file_parse (const char *path); extern void theme_free (struct t_theme *theme); +/* lifecycle: drop all contributions owned by a plugin or script */ +extern void theme_unregister_plugin (struct t_weechat_plugin *plugin); +extern void theme_unregister_script (struct t_weechat_plugin *plugin, + const void *script); + extern void theme_init (void); extern void theme_end (void); diff --git a/src/plugins/weechat-plugin.h b/src/plugins/weechat-plugin.h index 0afecab82..d11bdb289 100644 --- a/src/plugins/weechat-plugin.h +++ b/src/plugins/weechat-plugin.h @@ -730,7 +730,9 @@ struct t_weechat_plugin const char *option_name); /* themes */ - struct t_theme *(*theme_register) (const char *name, + struct t_theme *(*theme_register) (struct t_weechat_plugin *plugin, + const void *script, + const char *name, struct t_hashtable *overrides); /* key bindings */ @@ -1862,7 +1864,8 @@ extern int weechat_plugin_end (struct t_weechat_plugin *plugin); /* themes */ #define weechat_theme_register(__name, __overrides) \ - (weechat_plugin->theme_register)(__name, __overrides) + (weechat_plugin->theme_register)(weechat_plugin, NULL, \ + __name, __overrides) /* key bindings */ #define weechat_key_bind(__context, __keys) \ diff --git a/tests/unit/core/test-core-theme.cpp b/tests/unit/core/test-core-theme.cpp index 7d453d80f..2c618e6d3 100644 --- a/tests/unit/core/test-core-theme.cpp +++ b/tests/unit/core/test-core-theme.cpp @@ -99,7 +99,7 @@ TEST(CoreTheme, Search) POINTERS_EQUAL(NULL, theme_search (NULL)); overrides = make_overrides ("weechat.color.chat", "default", NULL, NULL); - theme_register ("dark", overrides); + theme_register (NULL, NULL, "dark", overrides); hashtable_free (overrides); /* registered name found */ @@ -165,8 +165,9 @@ TEST(CoreTheme, Alloc) LONGS_EQUAL(19, (long)strlen (theme->date)); CHECK(theme->weechat_version != NULL); CHECK(theme->weechat_version[0] != '\0'); - CHECK(theme->overrides != NULL); - LONGS_EQUAL(0, theme->overrides->items_count); + POINTERS_EQUAL(NULL, theme->contributions); + POINTERS_EQUAL(NULL, theme->last_contribution); + LONGS_EQUAL(0, theme_overrides_count (theme)); POINTERS_EQUAL(NULL, theme->prev_theme); POINTERS_EQUAL(NULL, theme->next_theme); @@ -202,40 +203,40 @@ TEST(CoreTheme, Register) struct t_theme *t1, *t2; /* NULL / empty name => NULL */ - POINTERS_EQUAL(NULL, theme_register (NULL, NULL)); - POINTERS_EQUAL(NULL, theme_register ("", NULL)); + POINTERS_EQUAL(NULL, theme_register (NULL, NULL, NULL, NULL)); + POINTERS_EQUAL(NULL, theme_register (NULL, NULL, "", NULL)); /* register a new theme */ o1 = make_overrides ("weechat.color.chat", "default", "weechat.color.separator", "blue"); - t1 = theme_register ("dark", o1); + t1 = theme_register (NULL, NULL, "dark", o1); hashtable_free (o1); CHECK(t1 != NULL); STRCMP_EQUAL("dark", t1->name); - LONGS_EQUAL(2, t1->overrides->items_count); - STRCMP_EQUAL("default", (const char *)hashtable_get (t1->overrides, + LONGS_EQUAL(2, theme_overrides_count (t1)); + STRCMP_EQUAL("default", theme_get_override (t1, "weechat.color.chat")); - STRCMP_EQUAL("blue", (const char *)hashtable_get (t1->overrides, + STRCMP_EQUAL("blue", theme_get_override (t1, "weechat.color.separator")); /* second call with same name merges into the existing theme */ o2 = make_overrides ("irc.color.input_nick", "lightcyan", "weechat.color.separator", "darkgray"); - t2 = theme_register ("dark", o2); + t2 = theme_register (NULL, NULL, "dark", o2); hashtable_free (o2); POINTERS_EQUAL(t1, t2); /* same struct, merged into */ - LONGS_EQUAL(3, t1->overrides->items_count); + LONGS_EQUAL(3, theme_overrides_count (t1)); /* new key added */ - STRCMP_EQUAL("lightcyan", (const char *)hashtable_get (t1->overrides, + STRCMP_EQUAL("lightcyan", theme_get_override (t1, "irc.color.input_nick")); /* duplicate key overridden */ - STRCMP_EQUAL("darkgray", (const char *)hashtable_get (t1->overrides, + STRCMP_EQUAL("darkgray", theme_get_override (t1, "weechat.color.separator")); /* registering with NULL overrides only creates the theme */ - t2 = theme_register ("empty", NULL); + t2 = theme_register (NULL, NULL, "empty", NULL); CHECK(t2 != NULL); - LONGS_EQUAL(0, t2->overrides->items_count); + LONGS_EQUAL(0, theme_overrides_count (t2)); } /* @@ -255,9 +256,9 @@ TEST(CoreTheme, List) arraylist_free (list); /* register three themes in non-alphabetical order */ - theme_register ("solarized", NULL); - theme_register ("dark", NULL); - theme_register ("nord", NULL); + theme_register (NULL, NULL, "solarized", NULL); + theme_register (NULL, NULL, "dark", NULL); + theme_register (NULL, NULL, "nord", NULL); list = theme_list (); CHECK(list != NULL); @@ -471,7 +472,7 @@ TEST(CoreTheme, Apply) /* register a theme that flips one themable option, then apply */ overrides = make_overrides ("weechat.look.prefix_error", "TEST!", NULL, NULL); - theme_register ("apply_test", overrides); + theme_register (NULL, NULL, "apply_test", overrides); hashtable_free (overrides); LONGS_EQUAL(WEECHAT_RC_OK, theme_apply ("apply_test")); @@ -584,18 +585,17 @@ TEST(CoreTheme, FileParse) /* [options] entries: three known keys, "unknown_info_key" must NOT leak in (it lives under [info]) */ - LONGS_EQUAL(3, theme->overrides->items_count); + LONGS_EQUAL(3, theme_overrides_count (theme)); STRCMP_EQUAL("default", - (const char *)hashtable_get (theme->overrides, + theme_get_override (theme, "weechat.color.chat")); STRCMP_EQUAL("blue", - (const char *)hashtable_get (theme->overrides, + theme_get_override (theme, "weechat.color.separator")); STRCMP_EQUAL("lightcyan", - (const char *)hashtable_get (theme->overrides, + theme_get_override (theme, "irc.color.input_nick")); - POINTERS_EQUAL(NULL, hashtable_get (theme->overrides, - "unknown_info_key")); + POINTERS_EQUAL(NULL, theme_get_override (theme, "unknown_info_key")); theme_free (theme); unlink (path); @@ -614,7 +614,7 @@ TEST(CoreTheme, FileParse) STRCMP_EQUAL("", theme->description); STRCMP_EQUAL("", theme->date); STRCMP_EQUAL("", theme->weechat_version); - LONGS_EQUAL(0, theme->overrides->items_count); + LONGS_EQUAL(0, theme_overrides_count (theme)); theme_free (theme); unlink (path); @@ -634,11 +634,11 @@ TEST(CoreTheme, FileParse) theme = theme_file_parse (path); CHECK(theme != NULL); STRCMP_EQUAL("robust", theme->name); - LONGS_EQUAL(1, theme->overrides->items_count); + LONGS_EQUAL(1, theme_overrides_count (theme)); STRCMP_EQUAL("red", - (const char *)hashtable_get (theme->overrides, + theme_get_override (theme, "weechat.color.chat")); - POINTERS_EQUAL(NULL, hashtable_get (theme->overrides, "ignored")); + POINTERS_EQUAL(NULL, theme_get_override (theme, "ignored")); theme_free (theme); unlink (path); } @@ -662,7 +662,7 @@ TEST(CoreTheme, Save) LONGS_EQUAL(WEECHAT_RC_ERROR, theme_save ("backup-anything", 1)); /* name colliding with a built-in is refused */ - theme_register ("dark", NULL); + theme_register (NULL, NULL, "dark", NULL); LONGS_EQUAL(WEECHAT_RC_ERROR, theme_save ("dark", 0)); LONGS_EQUAL(WEECHAT_RC_ERROR, theme_save ("dark", 1)); @@ -699,7 +699,7 @@ TEST(CoreTheme, Delete) LONGS_EQUAL(WEECHAT_RC_ERROR, theme_delete ("")); /* refuses to delete a built-in (no file to delete) */ - theme_register ("dark", NULL); + theme_register (NULL, NULL, "dark", NULL); LONGS_EQUAL(WEECHAT_RC_ERROR, theme_delete ("dark")); /* missing file => error */ @@ -725,7 +725,7 @@ TEST(CoreTheme, Delete) TEST(CoreTheme, Init) { /* register something so we can prove init wipes it */ - theme_register ("dark", NULL); + theme_register (NULL, NULL, "dark", NULL); CHECK(themes != NULL); theme_init (); @@ -741,8 +741,8 @@ TEST(CoreTheme, Init) TEST(CoreTheme, End) { - theme_register ("dark", NULL); - theme_register ("light", NULL); + theme_register (NULL, NULL, "dark", NULL); + theme_register (NULL, NULL, "light", NULL); CHECK(themes != NULL); theme_end (); @@ -750,6 +750,93 @@ TEST(CoreTheme, End) POINTERS_EQUAL(NULL, last_theme); } +/* + * Test functions: + * theme_unregister_plugin + * theme_unregister_script + * theme_register (per-contributor identity) + */ + +TEST(CoreTheme, UnregisterByOwner) +{ + struct t_weechat_plugin fake_plugin_a, fake_plugin_b; + int script_a = 0, script_b = 0; + struct t_hashtable *o1, *o2, *o3, *o4; + struct t_theme *theme; + + /* four contributors register against the same theme: + core (NULL, NULL), plugin_a (no script), plugin_b (no script), + and an individual script under plugin_a */ + o1 = make_overrides ("weechat.color.chat", "default", NULL, NULL); + o2 = make_overrides ("irc.color.input_nick", "cyan", NULL, NULL); + o3 = make_overrides ("fset.color.title_filter", "18", NULL, NULL); + o4 = make_overrides ("weechat.color.separator", "251", NULL, NULL); + + theme_register (NULL, NULL, "light", o1); + theme_register (&fake_plugin_a, NULL, "light", o2); + theme_register (&fake_plugin_b, NULL, "light", o3); + theme_register (&fake_plugin_a, &script_a, "light", o4); + + hashtable_free (o1); + hashtable_free (o2); + hashtable_free (o3); + hashtable_free (o4); + + theme = theme_search ("light"); + CHECK(theme != NULL); + LONGS_EQUAL(4, theme_overrides_count (theme)); + + /* dropping plugin_a's plugin-level contribution leaves core, + plugin_b, and plugin_a's script contributions intact */ + theme_unregister_plugin (&fake_plugin_a); + LONGS_EQUAL(3, theme_overrides_count (theme)); + STRCMP_EQUAL("default", + theme_get_override (theme, "weechat.color.chat")); + POINTERS_EQUAL(NULL, + theme_get_override (theme, "irc.color.input_nick")); + STRCMP_EQUAL("18", + theme_get_override (theme, "fset.color.title_filter")); + STRCMP_EQUAL("251", + theme_get_override (theme, "weechat.color.separator")); + + /* dropping the script contribution leaves only core and plugin_b */ + theme_unregister_script (&fake_plugin_a, &script_a); + LONGS_EQUAL(2, theme_overrides_count (theme)); + POINTERS_EQUAL(NULL, + theme_get_override (theme, "weechat.color.separator")); + + /* unrelated owners are no-ops */ + theme_unregister_plugin (&fake_plugin_a); /* already gone */ + theme_unregister_script (&fake_plugin_b, &script_b); /* never registered */ + LONGS_EQUAL(2, theme_overrides_count (theme)); +} + +TEST(CoreTheme, RegisterMergesPerContributor) +{ + struct t_weechat_plugin fake_plugin; + struct t_hashtable *a, *b; + struct t_theme *theme; + + /* two successive registrations from the same (plugin, script) + merge into a single contribution */ + a = make_overrides ("k1", "v1", "k2", "v2"); + b = make_overrides ("k2", "newv2", "k3", "v3"); + + theme_register (&fake_plugin, NULL, "X", a); + theme = theme_register (&fake_plugin, NULL, "X", b); + hashtable_free (a); + hashtable_free (b); + + CHECK(theme != NULL); + /* one contribution, 3 keys (k1, k2, k3) */ + CHECK(theme->contributions != NULL); + POINTERS_EQUAL(NULL, theme->contributions->next_contribution); + LONGS_EQUAL(3, theme->contributions->overrides->items_count); + STRCMP_EQUAL("v1", theme_get_override (theme, "k1")); + STRCMP_EQUAL("newv2", theme_get_override (theme, "k2")); + STRCMP_EQUAL("v3", theme_get_override (theme, "k3")); +} + /* * Test functions: * theme_builtin_init @@ -770,24 +857,24 @@ TEST(CoreTheme, BuiltinInit) CHECK(theme != NULL); /* sanity check: many core color overrides (>= 30) */ - CHECK(theme->overrides->items_count >= 30); + CHECK(theme_overrides_count (theme) >= 30); /* spot-check a few known entries from the core light table */ STRCMP_EQUAL("cyan", - (const char *)hashtable_get (theme->overrides, + theme_get_override (theme, "weechat.color.chat_nick")); STRCMP_EQUAL("251", - (const char *)hashtable_get (theme->overrides, + theme_get_override (theme, "weechat.color.separator")); STRCMP_EQUAL("254", - (const char *)hashtable_get (theme->overrides, + theme_get_override (theme, "weechat.bar.status.color_bg")); /* idempotency: a second call merges (no duplicate themes, count stays the same because the same keys are re-inserted) */ - int count_before = theme->overrides->items_count; + int count_before = theme_overrides_count (theme); theme_builtin_init (); theme = theme_search ("light"); CHECK(theme != NULL); - LONGS_EQUAL(count_before, theme->overrides->items_count); + LONGS_EQUAL(count_before, theme_overrides_count (theme)); }