1
0
mirror of https://github.com/weechat/weechat.git synced 2026-07-10 11:43:13 +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

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 plugin/script unload
lifecycle:

  - 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.

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 20260704-02 (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-07-04 20:20:17 +02:00
parent ce4fb15c8b
commit 34263a2e6e
6 changed files with 440 additions and 78 deletions
+1 -2
View File
@@ -7412,8 +7412,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;
+1 -1
View File
@@ -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);
}
+272 -28
View File
@@ -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;
}
/*
* Free one contribution (do 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);
}
/*
* Free a theme (do 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,92 @@ theme_merge_overrides_cb (void *data,
}
/*
* Register a theme by name with a set of option overrides.
* Search the contribution belonging to the given (plugin, script) pair
* in a theme's contribution list. Return 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;
}
/*
* Allocate a new contribution and append 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.
* Return 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;
}
/*
* Register 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
*
* Return 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.
*
* Return 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 +293,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;
}
/*
* Drop one contribution from a theme (unlink and free 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);
}
/*
* Drop all contributions owned by a given plugin (across every theme).
* Called from the plugin-unload lifecycle.
*
* 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;
}
}
}
/*
* Drop all contributions owned by a given script (across every theme).
* Called from the script-unload lifecycle.
*/
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;
}
}
}
/*
* Return 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;
}
/*
* Return 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;
}
/*
* Compare two themes by name (callback used by arraylist sort).
*
@@ -509,6 +737,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;
@@ -523,6 +752,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;
@@ -626,7 +864,7 @@ theme_file_parse (const char *path)
if (in_options)
{
hashtable_set (theme->overrides, key, value);
hashtable_set (contribution->overrides, key, value);
}
else
{
@@ -695,8 +933,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;
@@ -721,12 +959,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"),
@@ -735,7 +973,6 @@ theme_apply (const char *name)
free (path);
return WEECHAT_RC_ERROR;
}
overrides = registry_theme->overrides;
}
free (path);
@@ -756,10 +993,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 */
+34 -5
View File
@@ -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);