You don’t actually always have to look at the code. Since Nix is normally just used to generate the apps config file, you can configure the app how you want without Nix and then create your Nix config based on the config file the app generated.
Anki add-ons store user config in the meta.json
file under the config
key. Everything you put in .withConfig {}
gets translated to JSON and put there in meta.json
. This is also what Anki does when you edit an add-on’s config from within the UI. The JSON you type in gets put in meta.json
under the config
key.
If you look at the meta.json
for ReColor when not using Nix, it probably looks something like this:
{
"human_version": "3.0",
"name": "ReColor",
"mod": 1709430312,
"min_point_version": 55,
"max_point_version": 55,
"branch_index": 1,
"disabled": false,
"config": {
"colors": {
"ACCENT_CARD": [
"Card mode",
"#cba6f7",
"#cba6f7",
"--accent-card"
],
"ACCENT_DANGER": [
"Danger",
"#f38ba8",
"#f38ba8",
"--accent-danger"
],
"ACCENT_NOTE": [
"Note mode",
"#a6e3a1",
"#a6e3a1",
"--accent-note"
],
"BORDER": [
"Border",
"#45475a",
"#45475a",
"--border"
],
"BORDER_FOCUS": [
"Border (focused input)",
"#f5e0dc",
"#f5e0dc",
"--border-focus"
],
"BORDER_STRONG": [
"Border (strong)",
"#585b70",
"#585b70",
"--border-strong"
],
"BORDER_SUBTLE": [
"Border (subtle)",
"#313244",
"#313244",
"--border-subtle"
],
"BUTTON_BG": [
"Button background",
"#313244",
"#313244",
"--button-bg"
],
"BUTTON_DISABLED": [
"Button background (disabled)",
"#313244",
"#313244",
"--button-disabled"
],
"BUTTON_HOVER": [
"Button background (hover)",
"#45475a",
"#45475a",
[
"--button-gradient-start",
"--button-gradient-end"
]
],
"BUTTON_HOVER_BORDER": [
"Button border (hover)",
"#585b70",
"#585b70",
"--button-hover-border"
],
"BUTTON_PRIMARY_BG": [
"Button Primary Bg",
"#2f67e1",
"#2f67e1",
"--button-primary-bg"
],
"BUTTON_PRIMARY_DISABLED": [
"Button Primary Disabled",
"#4484ed",
"#4484ed",
"--button-primary-disabled"
],
"BUTTON_PRIMARY_GRADIENT_END": [
"Button Primary Gradient End",
"#2544a8",
"#2544a8",
"--button-primary-gradient-end"
],
"BUTTON_PRIMARY_GRADIENT_START": [
"Button Primary Gradient Start",
"#2f67e1",
"#2f67e1",
"--button-primary-gradient-start"
],
"CANVAS": [
"Background",
"#1e1e2e",
"#1e1e2e",
[
"--canvas",
"--bs-body-bg"
]
],
"CANVAS_CODE": [
"Code editor background",
"#181825",
"#181825",
"--canvas-code"
],
"CANVAS_ELEVATED": [
"Review",
"#181825",
"#181825",
"--canvas-elevated"
],
"CANVAS_GLASS": [
"Background (transparent text surface)",
"#18182566",
"#18182566",
"--canvas-glass"
],
"CANVAS_INSET": [
"Background (inset)",
"#11111b",
"#11111b",
"--canvas-inset"
],
"CANVAS_OVERLAY": [
"Background (menu & tooltip)",
"#181825",
"#181825",
"--canvas-overlay"
],
"FG": [
"Text",
"#cdd6f4",
"#cdd6f4",
[
"--fg",
"--bs-body-color"
]
],
"FG_DISABLED": [
"Text (disabled)",
"#a6adc6",
"#a6adc6",
"--fg-disabled"
],
"FG_FAINT": [
"Text (faint)",
"#9399b2",
"#9399b2",
"--fg-faint"
],
"FG_LINK": [
"Text (link)",
"#f5e0dc",
"#f5e0dc",
"--fg-link"
],
"FG_SUBTLE": [
"Text (subtle)",
"#bac2de",
"#bac2de",
"--fg-subtle"
],
"FLAG_1": [
"Flag 1",
"#f38ba8",
"#f38ba8",
"--flag-1"
],
"FLAG_2": [
"Flag 2",
"#fab387",
"#fab387",
"--flag-2"
],
"FLAG_3": [
"Flag 3",
"#a6e3a1",
"#a6e3a1",
"--flag-3"
],
"FLAG_4": [
"Flag 4",
"#89b4fa",
"#89b4fa",
"--flag-4"
],
"FLAG_5": [
"Flag 5",
"#cba6f7",
"#cba6f7",
"--flag-5"
],
"FLAG_6": [
"Flag 6",
"#89dceb",
"#89dceb",
"--flag-6"
],
"FLAG_7": [
"Flag 7",
"#c9cbff",
"#c9cbff",
"--flag-7"
],
"HIGHLIGHT_BG": [
"Highlight background",
"#313244",
"#313244",
"--highlight-bg"
],
"HIGHLIGHT_FG": [
"Highlight text",
"#f5e0dc",
"#f5e0dc",
"--highlight-fg"
],
"SCROLLBAR_BG": [
"Scrollbar background",
"#1e1e2e",
"#1e1e2e",
"--scrollbar-bg"
],
"SCROLLBAR_BG_ACTIVE": [
"Scrollbar background (active)",
"#313244",
"#313244",
"--scrollbar-bg-active"
],
"SCROLLBAR_BG_HOVER": [
"Scrollbar background (hover)",
"#1e1e2e",
"#1e1e2e",
"--scrollbar-bg-hover"
],
"SELECTED_BG": [
"Selected Bg",
"#313244",
"#313244",
"--selected-bg"
],
"SELECTED_FG": [
"Selected Fg",
"#f5e0dc",
"#f5e0dc",
"--selected-fg"
],
"SHADOW": [
"Shadow",
"#1e1e2e",
"#1e1e2e",
"--shadow"
],
"SHADOW_FOCUS": [
"Shadow (focused input)",
"#fab387",
"#fab387",
"--shadow-focus"
],
"SHADOW_INSET": [
"Shadow (inset)",
"#11111b",
"#11111b",
"--shadow-inset"
],
"SHADOW_SUBTLE": [
"Shadow (subtle)",
"#181825",
"#181825",
"--shadow-subtle"
],
"STATE_BURIED": [
"Buried",
"#7f849c",
"#7f849c",
"--state-buried"
],
"STATE_LEARN": [
"Learn",
"#f38ba8",
"#f38ba8",
"--state-learn"
],
"STATE_MARKED": [
"Marked",
"#FAE3B0",
"#FAE3B0",
"--state-marked"
],
"STATE_NEW": [
"New",
"#96cdfb",
"#96cdfb",
"--state-new"
],
"STATE_REVIEW": [
"Review",
"#a6e3a1",
"#a6e3a1",
"--state-review"
],
"STATE_SUSPENDED": [
"Suspended",
"#a6adc8",
"#a6adc8",
"--state-suspended"
]
},
"version": {
"major": 3,
"minor": 0
}
},
"conflicts": [],
"update_enabled": true
}
Look familiar? Everything under config
is the same format as the files you found in themes/
. So, clearly what you need to configure in withConfig
isn’t which theme file to load but rather, the theme itself. When manually selecting a theme in ReColor, what it actually does is load one of those presets in themes/
and then put its contents under config
in meta.json
.
Knowing this, it should be pretty easy to translate to Nix. Here’s what that looks like:
pkgs.ankiAddons.recolor.withConfig {
config = {
colors = {
ACCENT_CARD = [
"Card mode"
"#cba6f7"
"#cba6f7"
"--accent-card"
];
ACCENT_DANGER = [
"Danger"
"#f38ba8"
"#f38ba8"
"--accent-danger"
];
ACCENT_NOTE = [
"Note mode"
"#a6e3a1"
"#a6e3a1"
"--accent-note"
];
BORDER = [
"Border"
"#45475a"
"#45475a"
"--border"
];
BORDER_FOCUS = [
"Border (focused input)"
"#f5e0dc"
"#f5e0dc"
"--border-focus"
];
BORDER_STRONG = [
"Border (strong)"
"#585b70"
"#585b70"
"--border-strong"
];
BORDER_SUBTLE = [
"Border (subtle)"
"#313244"
"#313244"
"--border-subtle"
];
BUTTON_BG = [
"Button background"
"#313244"
"#313244"
"--button-bg"
];
BUTTON_DISABLED = [
"Button background (disabled)"
"#313244"
"#313244"
"--button-disabled"
];
BUTTON_HOVER = [
"Button background (hover)"
"#45475a"
"#45475a"
[
"--button-gradient-start"
"--button-gradient-end"
]
];
BUTTON_HOVER_BORDER = [
"Button border (hover)"
"#585b70"
"#585b70"
"--button-hover-border"
];
BUTTON_PRIMARY_BG = [
"Button Primary Bg"
"#2f67e1"
"#2f67e1"
"--button-primary-bg"
];
BUTTON_PRIMARY_DISABLED = [
"Button Primary Disabled"
"#4484ed"
"#4484ed"
"--button-primary-disabled"
];
BUTTON_PRIMARY_GRADIENT_END = [
"Button Primary Gradient End"
"#2544a8"
"#2544a8"
"--button-primary-gradient-end"
];
BUTTON_PRIMARY_GRADIENT_START = [
"Button Primary Gradient Start"
"#2f67e1"
"#2f67e1"
"--button-primary-gradient-start"
];
CANVAS = [
"Background"
"#1e1e2e"
"#1e1e2e"
[
"--canvas"
"--bs-body-bg"
]
];
CANVAS_CODE = [
"Code editor background"
"#181825"
"#181825"
"--canvas-code"
];
CANVAS_ELEVATED = [
"Review"
"#181825"
"#181825"
"--canvas-elevated"
];
CANVAS_GLASS = [
"Background (transparent text surface)"
"#18182566"
"#18182566"
"--canvas-glass"
];
CANVAS_INSET = [
"Background (inset)"
"#11111b"
"#11111b"
"--canvas-inset"
];
CANVAS_OVERLAY = [
"Background (menu & tooltip)"
"#181825"
"#181825"
"--canvas-overlay"
];
FG = [
"Text"
"#cdd6f4"
"#cdd6f4"
[
"--fg"
"--bs-body-color"
]
];
FG_DISABLED = [
"Text (disabled)"
"#a6adc6"
"#a6adc6"
"--fg-disabled"
];
FG_FAINT = [
"Text (faint)"
"#9399b2"
"#9399b2"
"--fg-faint"
];
FG_LINK = [
"Text (link)"
"#f5e0dc"
"#f5e0dc"
"--fg-link"
];
FG_SUBTLE = [
"Text (subtle)"
"#bac2de"
"#bac2de"
"--fg-subtle"
];
FLAG_1 = [
"Flag 1"
"#f38ba8"
"#f38ba8"
"--flag-1"
];
FLAG_2 = [
"Flag 2"
"#fab387"
"#fab387"
"--flag-2"
];
FLAG_3 = [
"Flag 3"
"#a6e3a1"
"#a6e3a1"
"--flag-3"
];
FLAG_4 = [
"Flag 4"
"#89b4fa"
"#89b4fa"
"--flag-4"
];
FLAG_5 = [
"Flag 5"
"#cba6f7"
"#cba6f7"
"--flag-5"
];
FLAG_6 = [
"Flag 6"
"#89dceb"
"#89dceb"
"--flag-6"
];
FLAG_7 = [
"Flag 7"
"#c9cbff"
"#c9cbff"
"--flag-7"
];
HIGHLIGHT_BG = [
"Highlight background"
"#313244"
"#313244"
"--highlight-bg"
];
HIGHLIGHT_FG = [
"Highlight text"
"#f5e0dc"
"#f5e0dc"
"--highlight-fg"
];
SCROLLBAR_BG = [
"Scrollbar background"
"#1e1e2e"
"#1e1e2e"
"--scrollbar-bg"
];
SCROLLBAR_BG_ACTIVE = [
"Scrollbar background (active)"
"#313244"
"#313244"
"--scrollbar-bg-active"
];
SCROLLBAR_BG_HOVER = [
"Scrollbar background (hover)"
"#1e1e2e"
"#1e1e2e"
"--scrollbar-bg-hover"
];
SELECTED_BG = [
"Selected Bg"
"#313244"
"#313244"
"--selected-bg"
];
SELECTED_FG = [
"Selected Fg"
"#f5e0dc"
"#f5e0dc"
"--selected-fg"
];
SHADOW = [
"Shadow"
"#1e1e2e"
"#1e1e2e"
"--shadow"
];
SHADOW_FOCUS = [
"Shadow (focused input)"
"#fab387"
"#fab387"
"--shadow-focus"
];
SHADOW_INSET = [
"Shadow (inset)"
"#11111b"
"#11111b"
"--shadow-inset"
];
SHADOW_SUBTLE = [
"Shadow (subtle)"
"#181825"
"#181825"
"--shadow-subtle"
];
STATE_BURIED = [
"Buried"
"#7f849c"
"#7f849c"
"--state-buried"
];
STATE_LEARN = [
"Learn"
"#f38ba8"
"#f38ba8"
"--state-learn"
];
STATE_MARKED = [
"Marked"
"#FAE3B0"
"#FAE3B0"
"--state-marked"
];
STATE_NEW = [
"New"
"#96cdfb"
"#96cdfb"
"--state-new"
];
STATE_REVIEW = [
"Review"
"#a6e3a1"
"#a6e3a1"
"--state-review"
];
STATE_SUSPENDED = [
"Suspended"
"#a6adc8"
"#a6adc8"
"--state-suspended"
];
};
version = {
major = 3;
minor = 1;
};
};
}
To convert this to use Stylix, all you have to do is replace the color strings like “#45475a” with the color variables Stylix provides.
For the question of “how to find available configuration options”, you probably already did most of the steps but I’ll summarize all of them:
- Look at the Nix documentation. Sometimes, not every option you need is exposed as a Nix option. In those cases, you usually use something like
extraConfig
that gets translated directly into the app’s native config format. With Anki add-ons, the function withConfig
is being used instead of the NixOS module system. What gets passed to withConfig
acts the same as an extraConfig
option would.
- Look at the app’s documentation. It will usually explain how to use its config file.
- Look at the config file generated by the app. If the app’s documentation won’t tell you anything then let the app tell you itself!
- Look at other people’s Nix configs. In this case, you could try searching “recolor .withConfig” on GitHub.
- Finally, like you started to do, you can look at the app’s source code. This can be difficult if you aren’t a programmer, since an app could be written in any number of programming languages. I’ll try to explain a general approach to this next.
The key to finding what you want in the source code is tracing the logic. You find something you recognize like how you found the file that deals with “config” stuff, and then trace the logic to what you’re looking for. In this case, the config options.
Almost every programming language is just a list of instructions for the computer that are read in order. Most of those instructions/lines of code are just telling the computer to go run some other code before going to the next instruction. Those are called function calls and in most languages look like name_of_function_with_code_to_run()
. The key here is the parenthesis. If you see someTextWithoutSpaces followed directly by parenthesis, it’s probably a function a call. The parenthesis may or may not have other text inside them.
If all code did was follow some instructions in order, apps would be like a movie, the same every time with no interactivity. The missing piece here is the actual logic. That logic usually uses the terms if
and else
. The code version of “Give me ice cream if it’s Wednesday, otherwise I’ll have salad” might look like
if current_day == "Wednesday":
buy_ice_cream()
else:
buy_salad()
in Python, the language ReColor is coded in.
You found a starting point earlier of that manager.py
file. To get to the configuration options from there you’d have to first look at what it does. The file is full of functions for saving and loading config files. each def something
is a function that will be called from other parts of the code. One of those functions is called get
. It’s used to get values from the config. Logically, whatever calls that function must know what config options to look for, so why not search for get()
in the code base? If you do you’ll find this: conf.get(f"colors.{conf_key}.0")
. It looks like this function call has something to do with colors. Promising, but there’s not a specific option really. What calls the function this line was in? It turns out a few places do, but they all have code like:
conf_keys = [
"FG",
"FG_SUBTLE",
"FG_DISABLED",
"FG_FAINT",
"FG_LINK",
"CANVAS",
"CANVAS_ELEVATED",
"CANVAS_INSET",
"CANVAS_OVERLAY",
"CANVAS_CODE",
"BORDER",
"BORDER_SUBTLE",
"BORDER_STRONG",
"BORDER_FOCUS",
]
There’s your config keys!
You might notice that Nix code seems kind of different from what I was describing and most code you’ll see. That’s because Nix is different. Some of those differences are because it’s what’s called a “functional” programming language and that it’s made specifically for NixOS use cases rather than general use. In the end, it does work the same, though. You just have to understand how.
Understanding how it all works is a long, maybe never ending journey but a great one to start on. I don’t have great resources for this but the first step is learning the Nix language syntax like you may have already done. Nix Language - Nix 2.28.5 Reference Manual
One thing that makes Nix tricky is that so much of what you interact with are interfaces from nixpkgs that go well beyond the base Nix language. In the end, it’s all Nix, though (and a lot Bash to be fair). Beyond pure Nix, the NixOS module system is a great thing to read up on. For documentation on all the stuff people use from nixpkgs in their Nix code, the nixpkgs manual is great.
This should have at least solved your Anki problem. Hopefully the rest was helpful in some way as well.
Good luck learning!