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.
Add a small core-theme-builtin.c module containing the core
contribution to the "light" theme: 33 overrides for
"weechat.bar.{status,title}.color_*" and "weechat.color.*" tuned for
light-background terminals.
theme_builtin_init() builds a hashtable from the static entry table and
calls theme_register("light", overrides), then frees the temporary
hashtable. It is called once from weechat_init right after theme_init.
Calling it twice is a no-op (the registry merges identical keys).
Default option values are NOT changed. Existing configs render exactly
as before; users opt in with "/theme apply light".
Add TEST(CoreTheme, BuiltinInit) covering:
- the "light" theme is absent before theme_builtin_init();
- it is present after, with >= 30 overrides;
- three spot-checked values match the source table;
- calling theme_builtin_init() a second time does not change the
override count.
Plugins contribute their own "light" overrides via weechat_theme_register
in subsequent commits.
Add two complementary subcommands:
/theme save <name> [-full]: writes a user theme file at
${weechat_config_dir}/themes/<name>.theme containing the current
themable options. By default only options whose value differs from
their default (config_file_option_has_changed) are written, which
keeps the file small and focused. Pass "-full" to write every
themable option (matches the format used by automatic backups).
Name validation: refuses any name matching a built-in theme (those
are reserved for in-memory registrations) and any name starting
with "backup-" (reserved for /theme apply backups). Both checks
print an error and abort without writing.
/theme delete <name>: removes ${weechat_config_dir}/themes/<name>.theme
via unlink. Refuses to delete a name registered as a built-in
theme (a built-in has no file on disk to delete, even if the user
has a shadowing file of the same name they cannot remove it this
way; they can rename or delete it manually).
The full-snapshot writer used by /theme apply backups is refactored
into theme_write_file (name, description, diff_only). It is reused
by theme_make_backup (diff_only=0) and theme_save (diff_only inverted
from the user's -full flag).
Bug fix while at it: the writer was previously calling
config_file_option_value_to_string (ptr_option, 0, 1, 0); the third
and fourth arguments are "use_colors" and "use_delimiters", so the
call inserted GUI color escape codes into the file output and skipped
quoting strings. Corrected to (ptr_option, 0, 0, 1) so plain text
with proper string quoting is written; the change also fixes the
content of files produced by theme_make_backup in the previous commit.
Add a small INI-style parser for *.theme files and wire it into the
/theme command so user themes living in directory "themes" inside the
WeeChat configuration directory can be applied (and inspected) without
ever being cached.
Parser (theme_file_parse in core-theme.c) accepts two sections:
[info]
name = "..." \ shown by /theme info; ignored for apply
description = "..." |
date = "..." |
weechat = "..." /
(unknown keys are ignored with a warning)
[options]
full.option.name = "value"
Surrounding single or double quotes around a value are stripped (same
rule used by the regular config file reader). The parsed result is a
heap-allocated t_theme; the caller frees with theme_free.
Resolution rule in theme_apply: if the path
"${weechat_config_dir}/themes/<name>.theme" is readable it is parsed
and used (file shadows any built-in of the same name); otherwise the
built-in registry is consulted. The transient t_theme is freed before
the final refresh, so user themes have no steady-state memory
footprint regardless of how many .theme files have accumulated.
/theme list now also scans the themes directory and appends user
files to the listing (each marked "(file)"). backup-*.theme are
hidden by default; pass "-backups" to include them.
/theme info <name> works for both sources: file path is shown when the
information comes from disk; "built-in (in-memory)" otherwise.
Implement /theme apply <name> for themes currently in the in-memory
registry. The file-shadowing branch (read a .theme file from
${weechat_config_dir}/themes/ when no built-in matches) is added in
the next commit together with the parser.
Apply algorithm (theme_apply in core-theme.c):
- Look up the theme in the registry; abort with an error if unknown.
- If weechat.look.theme_backup is on and the target name does not
begin with "backup-", write a full snapshot of every themable
option to ${weechat_config_dir}/themes/backup-<timestamp>.theme
via theme_make_backup; abort the apply if the backup cannot be
written, so the user can always undo.
- Iterate the theme's overrides with theme_applying=1 so the
per-option config_change_color skips its gui refresh; for each
entry look up the option, refuse it if missing or non-themable
(warning to core buffer), otherwise call config_file_option_set.
- Perform a single gui_color_init_weechat + gui_window_ask_refresh
at the end.
- Persist the active label in weechat.look.theme and send signal
"theme_applied" with the name as data.
Add the new option weechat.look.theme_backup (boolean, default on)
which controls the backup-or-abort behaviour described above.
Wire the new /theme apply subcommand into core-command.c with the
existing /theme registration; update help text accordingly.
Introduce a new module (core-theme.{c,h}) holding the in-memory registry
of built-in themes used by the upcoming /theme command:
- struct t_theme stores name, description, date and weechat version
captured at registration time, plus a hashtable of overrides keyed by
full option name (file.section.option) -> value string.
- theme_register (name, overrides) creates a new theme or merges the
given overrides into an existing one (later calls override duplicate
keys); this is the API plugins and scripts will use to contribute
per-theme color values.
- theme_search and theme_list provide lookup and ordered enumeration.
- theme_init / theme_end are called from weechat_init / weechat_end.
The theme_applying flag is declared here but not yet consumed (it will
gate config_change_color in the next commit to avoid N redundant
window refreshes during /theme apply).
User theme files are not handled by this module: they are read
transiently inside /theme apply (a later commit) and never cached.