1
0
mirror of https://github.com/weechat/weechat.git synced 2026-06-29 06:16:40 +02:00

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.
This commit is contained in:
Sébastien Helleu
2026-05-27 20:47:47 +02:00
parent d9da6a07f1
commit 6f979f2e95
6 changed files with 441 additions and 78 deletions
+127 -40
View File
@@ -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));
}