588 lines
20 KiB
Python
588 lines
20 KiB
Python
# -*- coding: utf-8 -*-
|
|
import os
|
|
import subprocess
|
|
from typing import Any, List, Dict, TYPE_CHECKING
|
|
|
|
from gi.repository import Gtk, GLib
|
|
|
|
from oomox_gui.i18n import translate
|
|
from oomox_gui.plugin_api import OomoxImportPlugin, OomoxExportPlugin
|
|
from oomox_gui.export_common import DialogWithExportPath
|
|
from oomox_gui.terminal import get_lightness
|
|
from oomox_gui.color import (
|
|
mix_theme_colors, hex_darker, color_list_from_hex, int_list_from_hex,
|
|
)
|
|
from oomox_gui.export_common import ExportConfig
|
|
from oomox_gui.config import USER_CONFIG_DIR, DEFAULT_ENCODING
|
|
from oomox_gui.theme_model import get_first_theme_option
|
|
from oomox_gui.theme_file_parser import ColorScheme
|
|
from oomox_gui.theme_file import ThemeT
|
|
|
|
if TYPE_CHECKING:
|
|
from oomox_gui.theme_model import ThemeModelSection
|
|
|
|
# Enable Base16 export if pystache and yaml are installed:
|
|
try:
|
|
import pystache # noqa
|
|
import yaml # type: ignore[import] # noqa
|
|
except ImportError:
|
|
# @TODO: replace to error dialog:
|
|
print(
|
|
"!! WARNING !! `pystache` and `python-yaml` need to be installed "
|
|
"for exporting Base16 themes"
|
|
)
|
|
|
|
class PluginBase(OomoxImportPlugin): # pylint: disable=abstract-method
|
|
pass
|
|
else:
|
|
class PluginBase(OomoxImportPlugin, OomoxExportPlugin): # type: ignore # pylint: disable=abstract-method
|
|
pass
|
|
|
|
|
|
Base16TemplateDataT = dict[str, str | int | float]
|
|
Base16ThemeT = dict[str, str]
|
|
|
|
|
|
PLUGIN_DIR = os.path.dirname(os.path.realpath(__file__))
|
|
USER_BASE16_DIR = os.path.join(
|
|
USER_CONFIG_DIR, "base16/"
|
|
)
|
|
USER_BASE16_TEMPLATES_DIR = os.path.join(
|
|
USER_BASE16_DIR, "templates/"
|
|
)
|
|
|
|
|
|
OOMOX_TO_BASE16_TRANSLATION = {
|
|
"TERMINAL_BACKGROUND": "base00",
|
|
"TERMINAL_FOREGROUND": "base05",
|
|
|
|
"TERMINAL_COLOR0": "base01",
|
|
"TERMINAL_COLOR1": "base08",
|
|
"TERMINAL_COLOR2": "base0B",
|
|
"TERMINAL_COLOR3": "base09",
|
|
"TERMINAL_COLOR4": "base0D",
|
|
"TERMINAL_COLOR5": "base0E",
|
|
"TERMINAL_COLOR6": "base0C",
|
|
"TERMINAL_COLOR7": "base06",
|
|
|
|
"TERMINAL_COLOR8": "base02",
|
|
"TERMINAL_COLOR9": "base08", # @TODO: lighter
|
|
"TERMINAL_COLOR10": "base0B", # @TODO: lighter
|
|
"TERMINAL_COLOR11": "base0A",
|
|
"TERMINAL_COLOR12": "base0D", # @TODO: lighter
|
|
"TERMINAL_COLOR13": "base0E", # @TODO: lighter
|
|
"TERMINAL_COLOR14": "base0C", # @TODO: lighter
|
|
"TERMINAL_COLOR15": "base07",
|
|
|
|
# 03, 04, 0F -- need to be generated from them on back conversion
|
|
}
|
|
|
|
|
|
def yaml_load(content: str) -> Any:
|
|
return yaml.load(content, Loader=yaml.SafeLoader)
|
|
|
|
|
|
def convert_oomox_to_base16(
|
|
colorscheme: ColorScheme,
|
|
theme_name: str | None = None
|
|
) -> Base16ThemeT:
|
|
theme_name_or_fallback: str = (
|
|
theme_name or colorscheme.get('NAME') or 'themix_base16' # type: ignore[assignment]
|
|
)
|
|
base16_theme: Base16ThemeT = {}
|
|
|
|
base16_theme["scheme-name"] = base16_theme["scheme-author"] = \
|
|
theme_name_or_fallback
|
|
base16_theme["scheme-slug"] = base16_theme["scheme-name"].split('/')[-1].lower()
|
|
|
|
for oomox_key, base16_key in OOMOX_TO_BASE16_TRANSLATION.items():
|
|
theme_value = str(colorscheme[oomox_key])
|
|
base16_theme[base16_key] = theme_value
|
|
|
|
base16_theme['base03'] = mix_theme_colors(
|
|
base16_theme['base00'], base16_theme['base05'], 0.5
|
|
)
|
|
|
|
if get_lightness(base16_theme['base01']) > get_lightness(base16_theme['base00']):
|
|
base16_theme['base04'] = hex_darker(base16_theme['base00'], 20)
|
|
else:
|
|
base16_theme['base04'] = hex_darker(base16_theme['base00'], -20)
|
|
|
|
base16_theme['base0F'] = hex_darker(mix_theme_colors(
|
|
base16_theme['base08'], base16_theme['base09'], 0.5
|
|
), 20)
|
|
|
|
for key, value in colorscheme.items():
|
|
base16_theme[f'themix_{key}'] = str(value)
|
|
|
|
# from pprint import pprint; pprint(base16_theme)
|
|
|
|
return base16_theme
|
|
|
|
|
|
def convert_base16_to_template_data(
|
|
base16_theme: Base16ThemeT
|
|
) -> Base16TemplateDataT:
|
|
base16_data: Base16TemplateDataT = {}
|
|
for key, value in base16_theme.items():
|
|
if not key.startswith('base'):
|
|
base16_data[key] = value
|
|
try:
|
|
# @TODO: check theme model for color types only:
|
|
color_list_from_hex(value)
|
|
int_list_from_hex(value)
|
|
except Exception:
|
|
continue
|
|
|
|
hex_key = key + '-hex'
|
|
base16_data[hex_key] = value
|
|
base16_data[hex_key + '-r'], \
|
|
base16_data[hex_key + '-g'], \
|
|
base16_data[hex_key + '-b'] = \
|
|
color_list_from_hex(value)
|
|
|
|
rgb_key = key + '-rgb'
|
|
base16_data[rgb_key + '-r'], \
|
|
base16_data[rgb_key + '-g'], \
|
|
base16_data[rgb_key + '-b'] = \
|
|
int_list_from_hex(value)
|
|
|
|
dec_key = key + '-dec'
|
|
base16_data[dec_key + '-r'], \
|
|
base16_data[dec_key + '-g'], \
|
|
base16_data[dec_key + '-b'] = \
|
|
[
|
|
channel/255 for channel in int_list_from_hex(value)
|
|
]
|
|
return base16_data
|
|
|
|
|
|
def render_base16_template(template_path: str, base16_theme: Base16ThemeT) -> str:
|
|
with open(template_path, encoding=DEFAULT_ENCODING) as template_file:
|
|
template = template_file.read()
|
|
base16_data = convert_base16_to_template_data(base16_theme)
|
|
result = pystache.render(template, base16_data)
|
|
return result
|
|
|
|
|
|
class ConfigKeys:
|
|
last_app = 'last_app'
|
|
last_variant = 'last_variant'
|
|
|
|
|
|
class Base16Template:
|
|
name: str
|
|
path: str
|
|
|
|
def __init__(self, path: str) -> None:
|
|
self.path = path
|
|
self.name = os.path.basename(path)
|
|
|
|
@property
|
|
def template_dir(self) -> str:
|
|
return os.path.join(
|
|
self.path, 'templates',
|
|
)
|
|
|
|
def get_config(self) -> Any:
|
|
config_path = os.path.join(
|
|
self.template_dir, 'config.yaml'
|
|
)
|
|
with open(config_path, encoding=DEFAULT_ENCODING) as config_file:
|
|
config = yaml_load(config_file.read())
|
|
return config
|
|
|
|
|
|
class Base16ExportDialog(DialogWithExportPath):
|
|
|
|
config_name: str = 'base16'
|
|
default_export_dir: str = os.path.join(os.environ['HOME'], 'documents')
|
|
|
|
available_apps: Dict[str, Base16Template] = {}
|
|
current_app: Base16Template
|
|
available_variants: List[str]
|
|
current_variant = None
|
|
templates_homepages: Dict[str, str]
|
|
output_filename: str
|
|
rendered_theme: str
|
|
|
|
_variants_changed_signal: int | None = None
|
|
|
|
@property
|
|
def _sorted_appnames(self) -> list[str]:
|
|
return list(sorted(self.available_apps.keys()))
|
|
|
|
def _get_app_variant_template_path(self) -> str:
|
|
if not self.current_variant:
|
|
raise RuntimeError("No `.current_variant` selected.")
|
|
return os.path.join(
|
|
self.current_app.template_dir, self.current_variant + '.mustache'
|
|
)
|
|
|
|
def save_last_export_path(self) -> None:
|
|
export_path = os.path.expanduser(
|
|
self.option_widgets[self.OPTIONS.DEFAULT_PATH].get_text() # type: ignore[attr-defined]
|
|
)
|
|
count_subdirs = 0
|
|
for char in self.output_filename:
|
|
if char in ('/', ):
|
|
count_subdirs += 1
|
|
new_destination_dir, *_rest = export_path.rsplit('/', 1 + count_subdirs)
|
|
default_path_config_name = f"{self.OPTIONS.DEFAULT_PATH}_{self.current_app.name}"
|
|
self.export_config[self.OPTIONS.DEFAULT_PATH] = \
|
|
self.export_config[default_path_config_name] = \
|
|
new_destination_dir
|
|
self.export_config.save()
|
|
|
|
def do_export(self) -> None:
|
|
export_path = os.path.expanduser(
|
|
self.option_widgets[self.OPTIONS.DEFAULT_PATH].get_text() # type: ignore[attr-defined]
|
|
)
|
|
parent_dir = os.path.dirname(export_path)
|
|
if not os.path.exists(parent_dir):
|
|
os.makedirs(parent_dir)
|
|
with open(export_path, "w", encoding=DEFAULT_ENCODING) as fobj:
|
|
fobj.write(self.rendered_theme)
|
|
self.save_last_export_path()
|
|
|
|
def base16_stuff(self) -> None:
|
|
# NAME
|
|
base16_theme = convert_oomox_to_base16(
|
|
theme_name=self.theme_name,
|
|
colorscheme=self.colorscheme
|
|
)
|
|
variant_config = self.current_app.get_config()[self.current_variant]
|
|
filename_prefix = variant_config.get("force_filename") or base16_theme['scheme-slug']
|
|
output_name = f"{filename_prefix}{variant_config['extension']}"
|
|
self.output_filename = os.path.join(
|
|
variant_config['output'] or '', output_name
|
|
)
|
|
default_path_config_name = f"{self.OPTIONS.DEFAULT_PATH}_{self.current_app.name}"
|
|
self.option_widgets[self.OPTIONS.DEFAULT_PATH].set_text( # type: ignore[attr-defined]
|
|
os.path.join(
|
|
self.export_config.get(
|
|
default_path_config_name,
|
|
self.export_config[self.OPTIONS.DEFAULT_PATH]
|
|
),
|
|
self.output_filename,
|
|
)
|
|
)
|
|
|
|
# RENDER
|
|
template_path = self._get_app_variant_template_path()
|
|
result = render_base16_template(template_path, base16_theme)
|
|
|
|
# OUTPUT
|
|
self.rendered_theme = result
|
|
self.set_text(result)
|
|
self.show_text()
|
|
|
|
self.save_last_export_path()
|
|
|
|
def _set_variant(self, variant: str) -> None:
|
|
self.current_variant = \
|
|
self.export_config[ConfigKeys.last_variant] = \
|
|
variant
|
|
|
|
def _on_app_changed(self, apps_dropdown: Gtk.ComboBox) -> None:
|
|
self.current_app = \
|
|
self.available_apps[self._sorted_appnames[apps_dropdown.get_active()]]
|
|
self.export_config[ConfigKeys.last_app] = self.current_app.name
|
|
|
|
config = self.current_app.get_config()
|
|
self.available_variants = list(config.keys())
|
|
if self._variants_changed_signal:
|
|
self._variants_dropdown.disconnect(self._variants_changed_signal)
|
|
self._variants_store.clear()
|
|
for variant in self.available_variants:
|
|
self._variants_store.append([variant, ])
|
|
self._variants_changed_signal = \
|
|
self._variants_dropdown.connect("changed", self._on_variant_changed)
|
|
|
|
variant = self.current_variant or self.export_config[ConfigKeys.last_variant]
|
|
if not variant or variant not in self.available_variants:
|
|
variant = self.available_variants[0]
|
|
self._set_variant(variant)
|
|
|
|
if not self.current_variant:
|
|
raise RuntimeError("No `.current_variant` selected.")
|
|
self._variants_dropdown.set_active(self.available_variants.index(self.current_variant))
|
|
|
|
url = self.templates_homepages.get(self.current_app.name)
|
|
self._homepage_button.set_sensitive(bool(url))
|
|
|
|
def _init_apps_dropdown(self) -> None:
|
|
options_store = Gtk.ListStore(str)
|
|
for app_name in self._sorted_appnames:
|
|
options_store.append([app_name, ])
|
|
self._apps_dropdown = Gtk.ComboBox.new_with_model(options_store)
|
|
renderer_text = Gtk.CellRendererText()
|
|
self._apps_dropdown.pack_start(renderer_text, True)
|
|
self._apps_dropdown.add_attribute(renderer_text, "text", 0)
|
|
|
|
self._apps_dropdown.connect("changed", self._on_app_changed)
|
|
GLib.idle_add(
|
|
self._apps_dropdown.set_active,
|
|
(self._sorted_appnames.index(self.current_app.name))
|
|
)
|
|
|
|
def _on_variant_changed(self, variants_dropdown: Gtk.ComboBox) -> None:
|
|
variant = self.available_variants[variants_dropdown.get_active()]
|
|
self._set_variant(variant)
|
|
self.base16_stuff()
|
|
|
|
def _init_variants_dropdown(self) -> None:
|
|
self._variants_store = Gtk.ListStore(str)
|
|
self._variants_dropdown = Gtk.ComboBox.new_with_model(self._variants_store)
|
|
renderer_text = Gtk.CellRendererText()
|
|
self._variants_dropdown.pack_start(renderer_text, True)
|
|
self._variants_dropdown.add_attribute(renderer_text, "text", 0)
|
|
|
|
def _on_homepage_button(self, _button: Gtk.Button) -> None:
|
|
url = self.templates_homepages[self.current_app.name]
|
|
cmd = ["xdg-open", url, ]
|
|
subprocess.Popen(cmd) # pylint: disable=consider-using-with
|
|
|
|
def __init__(self, *args: Any, **kwargs: Any) -> None: # pylint: disable=too-many-locals
|
|
super().__init__(
|
|
*args,
|
|
height=800, width=800,
|
|
headline=translate("Base16 Export Options…"),
|
|
**kwargs
|
|
)
|
|
self.label.set_text(
|
|
translate("Choose export options below and copy-paste the result.")
|
|
)
|
|
default_config = self.export_config.config
|
|
default_config.update({
|
|
ConfigKeys.last_variant: None,
|
|
ConfigKeys.last_app: None,
|
|
})
|
|
self.export_config = ExportConfig(
|
|
config_name='base16',
|
|
default_config=default_config,
|
|
)
|
|
|
|
if not os.path.exists(USER_BASE16_TEMPLATES_DIR):
|
|
os.makedirs(USER_BASE16_TEMPLATES_DIR)
|
|
|
|
system_templates_dir = os.path.abspath(
|
|
os.path.join(PLUGIN_DIR, 'templates')
|
|
)
|
|
templates_index_path = system_templates_dir + '.yaml'
|
|
with open(templates_index_path, encoding=DEFAULT_ENCODING) as templates_index_file:
|
|
self.templates_homepages = yaml_load(templates_index_file.read())
|
|
|
|
# APPS
|
|
for templates_dir in (system_templates_dir, USER_BASE16_TEMPLATES_DIR):
|
|
for template_name in os.listdir(templates_dir):
|
|
template = Base16Template(path=os.path.join(templates_dir, template_name))
|
|
self.available_apps[template.name] = template
|
|
current_app_name = self.export_config[ConfigKeys.last_app]
|
|
if not current_app_name or current_app_name not in self.available_apps:
|
|
current_app_name = self.export_config[ConfigKeys.last_app] = \
|
|
self._sorted_appnames[0]
|
|
self.current_app = self.available_apps[current_app_name]
|
|
|
|
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
apps_label = Gtk.Label(label=translate('_Application:'), use_underline=True)
|
|
self._init_apps_dropdown()
|
|
apps_label.set_mnemonic_widget(self._apps_dropdown)
|
|
hbox.add(apps_label)
|
|
hbox.add(self._apps_dropdown)
|
|
|
|
# VARIANTS
|
|
variant_label = Gtk.Label(label=translate('_Variant:'), use_underline=True)
|
|
self._init_variants_dropdown()
|
|
variant_label.set_mnemonic_widget(self._variants_dropdown)
|
|
hbox.add(variant_label)
|
|
hbox.add(self._variants_dropdown)
|
|
|
|
# HOMEPAGE
|
|
self._homepage_button = Gtk.Button(label=translate('Open _Homepage'), use_underline=True)
|
|
self._homepage_button.connect('clicked', self._on_homepage_button)
|
|
hbox.add(self._homepage_button)
|
|
|
|
self.options_box.add(hbox)
|
|
self.top_area.add(self.options_box)
|
|
self.options_box.show_all()
|
|
|
|
user_templates_label = Gtk.Label()
|
|
_userdir_markup = \
|
|
f'<a href="file://{USER_BASE16_TEMPLATES_DIR}">{USER_BASE16_TEMPLATES_DIR}</a>'
|
|
user_templates_label.set_markup(
|
|
translate('User templates can be added to {userdir}').format(userdir=_userdir_markup)
|
|
)
|
|
self.box.add(user_templates_label)
|
|
user_templates_label.show_all()
|
|
|
|
|
|
class Plugin(PluginBase):
|
|
|
|
name = 'base16'
|
|
|
|
display_name = translate('Base16')
|
|
user_presets_display_name = translate('Base16 User-Imported')
|
|
export_text = translate('Base16-Based Templates…')
|
|
import_text = translate('From Base16 YML Format')
|
|
about_text = translate(
|
|
'Access huge collection of color themes and '
|
|
'export templates for many apps, such as '
|
|
'Alacritty, Emacs, GTK4, KDE, VIM and many more.'
|
|
)
|
|
about_links = [
|
|
{
|
|
'name': translate('Homepage'),
|
|
'url': 'https://github.com/themix-project/themix-plugin-base16/',
|
|
},
|
|
]
|
|
|
|
export_dialog = Base16ExportDialog
|
|
file_extensions = ('.yml', '.yaml', )
|
|
plugin_theme_dir = os.path.abspath(
|
|
os.path.join(PLUGIN_DIR, 'schemes')
|
|
)
|
|
|
|
theme_model_import: 'ThemeModelSection' = [
|
|
{
|
|
'display_name': translate('Base16 Import Options'),
|
|
'type': 'separator',
|
|
'value_filter': {
|
|
'FROM_PLUGIN': name,
|
|
},
|
|
},
|
|
{
|
|
'key': 'BASE16_GENERATE_DARK',
|
|
'type': 'bool',
|
|
'fallback_value': False,
|
|
'display_name': translate('Inverse GUI Variant'),
|
|
'reload_theme': True,
|
|
},
|
|
{
|
|
'key': 'BASE16_INVERT_TERMINAL',
|
|
'type': 'bool',
|
|
'fallback_value': False,
|
|
'display_name': translate('Inverse Terminal Colors'),
|
|
'reload_theme': True,
|
|
},
|
|
{
|
|
'key': 'BASE16_MILD_TERMINAL',
|
|
'type': 'bool',
|
|
'fallback_value': False,
|
|
'display_name': translate('Mild Terminal Colors'),
|
|
'reload_theme': True,
|
|
},
|
|
{
|
|
'display_name': translate('Edit Imported Theme'),
|
|
'type': 'separator',
|
|
'value_filter': {
|
|
'FROM_PLUGIN': name,
|
|
},
|
|
},
|
|
]
|
|
theme_model_gtk = [
|
|
{
|
|
'display_name': translate('Edit Generated Theme'),
|
|
'type': 'separator',
|
|
},
|
|
]
|
|
|
|
default_theme = {
|
|
"TERMINAL_THEME_MODE": "manual",
|
|
}
|
|
translation_common = {}
|
|
translation_common.update(OOMOX_TO_BASE16_TRANSLATION)
|
|
translation_light = {
|
|
"BG": "base05",
|
|
"FG": "base00",
|
|
"HDR_BG": "base04",
|
|
"HDR_FG": "base01",
|
|
"SEL_BG": "base0D",
|
|
"SEL_FG": "base00",
|
|
"ACCENT_BG": "base0D",
|
|
"TXT_BG": "base06",
|
|
"TXT_FG": "base01",
|
|
"BTN_BG": "base03",
|
|
"BTN_FG": "base07",
|
|
"HDR_BTN_BG": "base05",
|
|
"HDR_BTN_FG": "base01",
|
|
|
|
"ICONS_LIGHT_FOLDER": "base0C",
|
|
"ICONS_LIGHT": "base0C",
|
|
"ICONS_MEDIUM": "base0D",
|
|
"ICONS_DARK": "base03",
|
|
}
|
|
translation_dark = {
|
|
"BG": "base01",
|
|
"FG": "base06",
|
|
"HDR_BG": "base00",
|
|
"HDR_FG": "base05",
|
|
"SEL_BG": "base0E",
|
|
"SEL_FG": "base00",
|
|
"ACCENT_BG": "base0E",
|
|
"TXT_BG": "base02",
|
|
"TXT_FG": "base07",
|
|
"BTN_BG": "base00",
|
|
"BTN_FG": "base05",
|
|
"HDR_BTN_BG": "base01",
|
|
"HDR_BTN_FG": "base05",
|
|
|
|
"ICONS_LIGHT_FOLDER": "base0D",
|
|
"ICONS_LIGHT": "base0D",
|
|
"ICONS_MEDIUM": "base0E",
|
|
"ICONS_DARK": "base00",
|
|
}
|
|
translation_terminal_inverse = {
|
|
"TERMINAL_BACKGROUND": "base06",
|
|
"TERMINAL_FOREGROUND": "base01",
|
|
}
|
|
translation_terminal_mild = {
|
|
"TERMINAL_COLOR8": "base01",
|
|
"TERMINAL_COLOR15": "base06",
|
|
"TERMINAL_BACKGROUND": "base07",
|
|
"TERMINAL_FOREGROUND": "base02",
|
|
}
|
|
translation_terminal_mild_inverse = {
|
|
"TERMINAL_COLOR8": "base01",
|
|
"TERMINAL_COLOR15": "base06",
|
|
"TERMINAL_BACKGROUND": "base02",
|
|
"TERMINAL_FOREGROUND": "base07",
|
|
}
|
|
|
|
def read_colorscheme_from_path(self, preset_path: str) -> ThemeT:
|
|
|
|
base16_theme = {}
|
|
with open(preset_path, encoding=DEFAULT_ENCODING) as preset_file:
|
|
for line in preset_file.readlines():
|
|
try:
|
|
key, value, *_rest = line.split()
|
|
key = key.rstrip(':')
|
|
value = value.strip('\'"').lower()
|
|
base16_theme[key] = value
|
|
except Exception:
|
|
pass
|
|
|
|
oomox_theme: ThemeT = {}
|
|
oomox_theme.update(self.default_theme)
|
|
translation = {}
|
|
translation.update(self.translation_common)
|
|
|
|
if get_first_theme_option('BASE16_GENERATE_DARK', {}).get('fallback_value'):
|
|
translation.update(self.translation_dark)
|
|
else:
|
|
translation.update(self.translation_light)
|
|
|
|
if get_first_theme_option('BASE16_INVERT_TERMINAL', {}).get('fallback_value'):
|
|
translation.update(self.translation_terminal_inverse)
|
|
|
|
if get_first_theme_option('BASE16_MILD_TERMINAL', {}).get('fallback_value'):
|
|
if get_first_theme_option('BASE16_INVERT_TERMINAL', {}).get('fallback_value'):
|
|
translation.update(self.translation_terminal_mild)
|
|
else:
|
|
translation.update(self.translation_terminal_mild_inverse)
|
|
|
|
for oomox_key, base16_key in translation.items():
|
|
if base16_key in base16_theme:
|
|
oomox_theme[oomox_key] = base16_theme[base16_key]
|
|
return oomox_theme
|