retrovim 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/.conda/retrovim.yaml +259 -0
  2. package/.conda/terax.icns +0 -0
  3. package/.conda/terax.ico +0 -0
  4. package/.conda/terax.png +0 -0
  5. package/.github/workflows/release.yml +95 -0
  6. package/.gitmodules +3 -0
  7. package/.npm/kanata_touchcursor.js +28 -0
  8. package/.npm/nvim.js +26 -0
  9. package/.npm/yazi.js +26 -0
  10. package/.npm/zsh.js +17 -0
  11. package/.pip/kanata_touchcursor/__init__.py +28 -0
  12. package/.pip/nvim/__init__.py +31 -0
  13. package/.pip/yazi/__init__.py +31 -0
  14. package/.pip/zsh/__init__.py +22 -0
  15. package/CHANGELOG.md +26 -0
  16. package/LICENSE +674 -0
  17. package/README.md +336 -0
  18. package/kanata/touchcursor.kbd +164 -0
  19. package/nvim/init.lua +813 -0
  20. package/nvim/plugins/mini.nvim/.github/DISCUSSION_TEMPLATE/q-a.yml +72 -0
  21. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/bug-report.yml +136 -0
  22. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/config.yml +5 -0
  23. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/feature-request.yml +75 -0
  24. package/nvim/plugins/mini.nvim/.github/PULL_REQUEST_TEMPLATE.md +2 -0
  25. package/nvim/plugins/mini.nvim/.github/workflows/quality-control.yml +150 -0
  26. package/nvim/plugins/mini.nvim/.pre-commit-config.yaml +20 -0
  27. package/nvim/plugins/mini.nvim/.stylua.toml +7 -0
  28. package/nvim/plugins/mini.nvim/.styluaignore +1 -0
  29. package/nvim/plugins/mini.nvim/CHANGELOG.md +1894 -0
  30. package/nvim/plugins/mini.nvim/CODE_OF_CONDUCT.md +132 -0
  31. package/nvim/plugins/mini.nvim/CONTRIBUTING.md +331 -0
  32. package/nvim/plugins/mini.nvim/LICENSE +21 -0
  33. package/nvim/plugins/mini.nvim/MAINTAINING.md +330 -0
  34. package/nvim/plugins/mini.nvim/Makefile +51 -0
  35. package/nvim/plugins/mini.nvim/README.md +261 -0
  36. package/nvim/plugins/mini.nvim/TESTING.md +1011 -0
  37. package/nvim/plugins/mini.nvim/benchmarks/starter/README.md +35 -0
  38. package/nvim/plugins/mini.nvim/benchmarks/starter/benchmark.sh +56 -0
  39. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-alpha.lua +21 -0
  40. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-original.lua +17 -0
  41. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-starter.lua +18 -0
  42. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_empty.lua +4 -0
  43. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_starter-default.lua +7 -0
  44. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-alpha.lua +10 -0
  45. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-original.lua +7 -0
  46. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-starter.lua +20 -0
  47. package/nvim/plugins/mini.nvim/benchmarks/starter/install.sh +10 -0
  48. package/nvim/plugins/mini.nvim/benchmarks/starter/make_summary.py +68 -0
  49. package/nvim/plugins/mini.nvim/benchmarks/starter/startup-summary.md +10 -0
  50. package/nvim/plugins/mini.nvim/colors/miniautumn.lua +55 -0
  51. package/nvim/plugins/mini.nvim/colors/minicyan.lua +95 -0
  52. package/nvim/plugins/mini.nvim/colors/minischeme.lua +95 -0
  53. package/nvim/plugins/mini.nvim/colors/minispring.lua +53 -0
  54. package/nvim/plugins/mini.nvim/colors/minisummer.lua +49 -0
  55. package/nvim/plugins/mini.nvim/colors/miniwinter.lua +51 -0
  56. package/nvim/plugins/mini.nvim/colors/randomhue.lua +16 -0
  57. package/nvim/plugins/mini.nvim/doc/mini-ai.txt +889 -0
  58. package/nvim/plugins/mini.nvim/doc/mini-align.txt +923 -0
  59. package/nvim/plugins/mini.nvim/doc/mini-animate.txt +937 -0
  60. package/nvim/plugins/mini.nvim/doc/mini-base16.txt +285 -0
  61. package/nvim/plugins/mini.nvim/doc/mini-basics.txt +359 -0
  62. package/nvim/plugins/mini.nvim/doc/mini-bracketed.txt +622 -0
  63. package/nvim/plugins/mini.nvim/doc/mini-bufremove.txt +123 -0
  64. package/nvim/plugins/mini.nvim/doc/mini-clue.txt +760 -0
  65. package/nvim/plugins/mini.nvim/doc/mini-cmdline.txt +392 -0
  66. package/nvim/plugins/mini.nvim/doc/mini-colors.txt +880 -0
  67. package/nvim/plugins/mini.nvim/doc/mini-comment.txt +230 -0
  68. package/nvim/plugins/mini.nvim/doc/mini-completion.txt +477 -0
  69. package/nvim/plugins/mini.nvim/doc/mini-cursorword.txt +105 -0
  70. package/nvim/plugins/mini.nvim/doc/mini-deps.txt +590 -0
  71. package/nvim/plugins/mini.nvim/doc/mini-diff.txt +661 -0
  72. package/nvim/plugins/mini.nvim/doc/mini-doc.txt +434 -0
  73. package/nvim/plugins/mini.nvim/doc/mini-extra.txt +856 -0
  74. package/nvim/plugins/mini.nvim/doc/mini-files.txt +1018 -0
  75. package/nvim/plugins/mini.nvim/doc/mini-fuzzy.txt +160 -0
  76. package/nvim/plugins/mini.nvim/doc/mini-git.txt +425 -0
  77. package/nvim/plugins/mini.nvim/doc/mini-hipatterns.txt +489 -0
  78. package/nvim/plugins/mini.nvim/doc/mini-hues.txt +420 -0
  79. package/nvim/plugins/mini.nvim/doc/mini-icons.txt +508 -0
  80. package/nvim/plugins/mini.nvim/doc/mini-indentscope.txt +485 -0
  81. package/nvim/plugins/mini.nvim/doc/mini-input.txt +932 -0
  82. package/nvim/plugins/mini.nvim/doc/mini-jump.txt +202 -0
  83. package/nvim/plugins/mini.nvim/doc/mini-jump2d.txt +525 -0
  84. package/nvim/plugins/mini.nvim/doc/mini-keymap.txt +449 -0
  85. package/nvim/plugins/mini.nvim/doc/mini-map.txt +717 -0
  86. package/nvim/plugins/mini.nvim/doc/mini-misc.txt +431 -0
  87. package/nvim/plugins/mini.nvim/doc/mini-move.txt +181 -0
  88. package/nvim/plugins/mini.nvim/doc/mini-notify.txt +434 -0
  89. package/nvim/plugins/mini.nvim/doc/mini-nvim.txt +431 -0
  90. package/nvim/plugins/mini.nvim/doc/mini-operators.txt +472 -0
  91. package/nvim/plugins/mini.nvim/doc/mini-pairs.txt +346 -0
  92. package/nvim/plugins/mini.nvim/doc/mini-pick.txt +1535 -0
  93. package/nvim/plugins/mini.nvim/doc/mini-sessions.txt +239 -0
  94. package/nvim/plugins/mini.nvim/doc/mini-snippets.txt +1291 -0
  95. package/nvim/plugins/mini.nvim/doc/mini-splitjoin.txt +536 -0
  96. package/nvim/plugins/mini.nvim/doc/mini-starter.txt +618 -0
  97. package/nvim/plugins/mini.nvim/doc/mini-statusline.txt +361 -0
  98. package/nvim/plugins/mini.nvim/doc/mini-surround.txt +863 -0
  99. package/nvim/plugins/mini.nvim/doc/mini-tabline.txt +151 -0
  100. package/nvim/plugins/mini.nvim/doc/mini-test.txt +966 -0
  101. package/nvim/plugins/mini.nvim/doc/mini-trailspace.txt +91 -0
  102. package/nvim/plugins/mini.nvim/doc/mini-visits.txt +937 -0
  103. package/nvim/plugins/mini.nvim/logo.png +0 -0
  104. package/nvim/plugins/mini.nvim/lua/mini/ai.lua +2244 -0
  105. package/nvim/plugins/mini.nvim/lua/mini/align.lua +2076 -0
  106. package/nvim/plugins/mini.nvim/lua/mini/animate.lua +2127 -0
  107. package/nvim/plugins/mini.nvim/lua/mini/base16.lua +1752 -0
  108. package/nvim/plugins/mini.nvim/lua/mini/basics.lua +772 -0
  109. package/nvim/plugins/mini.nvim/lua/mini/bracketed.lua +2013 -0
  110. package/nvim/plugins/mini.nvim/lua/mini/bufremove.lua +283 -0
  111. package/nvim/plugins/mini.nvim/lua/mini/clue.lua +2096 -0
  112. package/nvim/plugins/mini.nvim/lua/mini/cmdline.lua +1182 -0
  113. package/nvim/plugins/mini.nvim/lua/mini/colors.lua +2483 -0
  114. package/nvim/plugins/mini.nvim/lua/mini/comment.lua +586 -0
  115. package/nvim/plugins/mini.nvim/lua/mini/completion.lua +2094 -0
  116. package/nvim/plugins/mini.nvim/lua/mini/cursorword.lua +311 -0
  117. package/nvim/plugins/mini.nvim/lua/mini/deps.lua +1660 -0
  118. package/nvim/plugins/mini.nvim/lua/mini/diff.lua +1938 -0
  119. package/nvim/plugins/mini.nvim/lua/mini/doc.lua +1371 -0
  120. package/nvim/plugins/mini.nvim/lua/mini/extra.lua +2290 -0
  121. package/nvim/plugins/mini.nvim/lua/mini/files.lua +3145 -0
  122. package/nvim/plugins/mini.nvim/lua/mini/fuzzy.lua +356 -0
  123. package/nvim/plugins/mini.nvim/lua/mini/git.lua +1737 -0
  124. package/nvim/plugins/mini.nvim/lua/mini/hipatterns.lua +1042 -0
  125. package/nvim/plugins/mini.nvim/lua/mini/hues.lua +2088 -0
  126. package/nvim/plugins/mini.nvim/lua/mini/icons.lua +2254 -0
  127. package/nvim/plugins/mini.nvim/lua/mini/indentscope.lua +1158 -0
  128. package/nvim/plugins/mini.nvim/lua/mini/init.lua +427 -0
  129. package/nvim/plugins/mini.nvim/lua/mini/input.lua +2183 -0
  130. package/nvim/plugins/mini.nvim/lua/mini/jump.lua +609 -0
  131. package/nvim/plugins/mini.nvim/lua/mini/jump2d.lua +1244 -0
  132. package/nvim/plugins/mini.nvim/lua/mini/keymap.lua +880 -0
  133. package/nvim/plugins/mini.nvim/lua/mini/map.lua +1709 -0
  134. package/nvim/plugins/mini.nvim/lua/mini/misc.lua +969 -0
  135. package/nvim/plugins/mini.nvim/lua/mini/move.lua +509 -0
  136. package/nvim/plugins/mini.nvim/lua/mini/notify.lua +938 -0
  137. package/nvim/plugins/mini.nvim/lua/mini/operators.lua +1349 -0
  138. package/nvim/plugins/mini.nvim/lua/mini/pairs.lua +657 -0
  139. package/nvim/plugins/mini.nvim/lua/mini/pick.lua +3876 -0
  140. package/nvim/plugins/mini.nvim/lua/mini/sessions.lua +689 -0
  141. package/nvim/plugins/mini.nvim/lua/mini/snippets.lua +2828 -0
  142. package/nvim/plugins/mini.nvim/lua/mini/splitjoin.lua +1132 -0
  143. package/nvim/plugins/mini.nvim/lua/mini/starter.lua +1599 -0
  144. package/nvim/plugins/mini.nvim/lua/mini/statusline.lua +686 -0
  145. package/nvim/plugins/mini.nvim/lua/mini/surround.lua +2388 -0
  146. package/nvim/plugins/mini.nvim/lua/mini/tabline.lua +564 -0
  147. package/nvim/plugins/mini.nvim/lua/mini/test.lua +2516 -0
  148. package/nvim/plugins/mini.nvim/lua/mini/trailspace.lua +215 -0
  149. package/nvim/plugins/mini.nvim/lua/mini/visits.lua +1577 -0
  150. package/nvim/plugins/mini.nvim/readmes/mini-ai.md +190 -0
  151. package/nvim/plugins/mini.nvim/readmes/mini-align.md +203 -0
  152. package/nvim/plugins/mini.nvim/readmes/mini-animate.md +214 -0
  153. package/nvim/plugins/mini.nvim/readmes/mini-base16.md +198 -0
  154. package/nvim/plugins/mini.nvim/readmes/mini-basics.md +184 -0
  155. package/nvim/plugins/mini.nvim/readmes/mini-bracketed.md +193 -0
  156. package/nvim/plugins/mini.nvim/readmes/mini-bufremove.md +139 -0
  157. package/nvim/plugins/mini.nvim/readmes/mini-clue.md +260 -0
  158. package/nvim/plugins/mini.nvim/readmes/mini-cmdline.md +183 -0
  159. package/nvim/plugins/mini.nvim/readmes/mini-colors.md +169 -0
  160. package/nvim/plugins/mini.nvim/readmes/mini-comment.md +183 -0
  161. package/nvim/plugins/mini.nvim/readmes/mini-completion.md +217 -0
  162. package/nvim/plugins/mini.nvim/readmes/mini-cursorword.md +137 -0
  163. package/nvim/plugins/mini.nvim/readmes/mini-deps.md +271 -0
  164. package/nvim/plugins/mini.nvim/readmes/mini-diff.md +289 -0
  165. package/nvim/plugins/mini.nvim/readmes/mini-doc.md +198 -0
  166. package/nvim/plugins/mini.nvim/readmes/mini-extra.md +158 -0
  167. package/nvim/plugins/mini.nvim/readmes/mini-files.md +247 -0
  168. package/nvim/plugins/mini.nvim/readmes/mini-fuzzy.md +134 -0
  169. package/nvim/plugins/mini.nvim/readmes/mini-git.md +168 -0
  170. package/nvim/plugins/mini.nvim/readmes/mini-hipatterns.md +176 -0
  171. package/nvim/plugins/mini.nvim/readmes/mini-hues.md +285 -0
  172. package/nvim/plugins/mini.nvim/readmes/mini-icons.md +183 -0
  173. package/nvim/plugins/mini.nvim/readmes/mini-indentscope.md +191 -0
  174. package/nvim/plugins/mini.nvim/readmes/mini-input.md +163 -0
  175. package/nvim/plugins/mini.nvim/readmes/mini-jump.md +161 -0
  176. package/nvim/plugins/mini.nvim/readmes/mini-jump2d.md +197 -0
  177. package/nvim/plugins/mini.nvim/readmes/mini-keymap.md +205 -0
  178. package/nvim/plugins/mini.nvim/readmes/mini-map.md +191 -0
  179. package/nvim/plugins/mini.nvim/readmes/mini-misc.md +145 -0
  180. package/nvim/plugins/mini.nvim/readmes/mini-move.md +163 -0
  181. package/nvim/plugins/mini.nvim/readmes/mini-notify.md +175 -0
  182. package/nvim/plugins/mini.nvim/readmes/mini-operators.md +193 -0
  183. package/nvim/plugins/mini.nvim/readmes/mini-pairs.md +158 -0
  184. package/nvim/plugins/mini.nvim/readmes/mini-pick.md +340 -0
  185. package/nvim/plugins/mini.nvim/readmes/mini-sessions.md +164 -0
  186. package/nvim/plugins/mini.nvim/readmes/mini-snippets.md +373 -0
  187. package/nvim/plugins/mini.nvim/readmes/mini-splitjoin.md +194 -0
  188. package/nvim/plugins/mini.nvim/readmes/mini-starter.md +176 -0
  189. package/nvim/plugins/mini.nvim/readmes/mini-statusline.md +155 -0
  190. package/nvim/plugins/mini.nvim/readmes/mini-surround.md +190 -0
  191. package/nvim/plugins/mini.nvim/readmes/mini-tabline.md +156 -0
  192. package/nvim/plugins/mini.nvim/readmes/mini-test.md +175 -0
  193. package/nvim/plugins/mini.nvim/readmes/mini-trailspace.md +137 -0
  194. package/nvim/plugins/mini.nvim/readmes/mini-visits.md +243 -0
  195. package/nvim/plugins/mini.nvim/scripts/dual_log.sh +13 -0
  196. package/nvim/plugins/mini.nvim/scripts/dual_push.sh +15 -0
  197. package/nvim/plugins/mini.nvim/scripts/dual_release.sh +102 -0
  198. package/nvim/plugins/mini.nvim/scripts/dual_sync.sh +115 -0
  199. package/nvim/plugins/mini.nvim/scripts/init-deps-example.lua +59 -0
  200. package/nvim/plugins/mini.nvim/scripts/lint-filename-length.sh +16 -0
  201. package/nvim/plugins/mini.nvim/scripts/lintcommit-ci.sh +18 -0
  202. package/nvim/plugins/mini.nvim/scripts/lintcommit.lua +393 -0
  203. package/nvim/plugins/mini.nvim/scripts/minidoc.lua +57 -0
  204. package/nvim/plugins/mini.nvim/scripts/minimal_init.lua +28 -0
  205. package/nvim/plugins/mini.nvim/scripts/minitest.lua +4 -0
  206. package/package.json +16 -0
  207. package/pyproject.toml +43 -0
  208. package/yazi/init.lua +12 -0
  209. package/yazi/keymap.toml +114 -0
  210. package/yazi/lesskey +5 -0
  211. package/yazi/plugins/bookmarks.yazi/main.lua +86 -0
  212. package/yazi/plugins/cd-git-root.yazi/main.lua +31 -0
  213. package/yazi/plugins/smart-enter.yazi/main.lua +11 -0
  214. package/yazi/theme.toml +26 -0
  215. package/yazi/yazi.toml +15 -0
  216. package/zsh/.zshrc +145 -0
  217. package/zsh/starship.toml +20 -0
  218. package/zsh/zsh-patina.toml +2 -0
@@ -0,0 +1,2483 @@
1
+ --- *mini.colors* Tweak and save any color scheme
2
+ ---
3
+ --- MIT License Copyright (c) 2023 Evgeni Chasnovski
4
+
5
+ --- Features:
6
+ --- - Create colorscheme object: either manually (|MiniColors.as_colorscheme()|)
7
+ --- or by querying present color schemes (including currently active one; see
8
+ --- |MiniColors.get_colorscheme()|).
9
+ ---
10
+ --- - Infer data about color scheme and/or modify based on it:
11
+ --- - Add transparency by removing background color (requires transparency
12
+ --- in terminal emulator).
13
+ --- - Infer cterm attributes (|cterm-colors|) based on gui colors making it
14
+ --- compatible with |'notermguicolors'|.
15
+ --- - Resolve highlight group links (|:highlight-link|).
16
+ --- - Compress by removing redundant highlight groups.
17
+ --- - Extract palette of used colors and/or infer terminal colors
18
+ --- (|terminal-config|) based on it.
19
+ ---
20
+ --- - Modify colors to better fit your taste and/or goals (see more in
21
+ --- |MiniColors-colorscheme-methods|):
22
+ --- - Apply any function to color hex string.
23
+ --- - Update channels (like lightness, saturation, hue, temperature, red,
24
+ --- green, blue, etc.; see more in |MiniColors-channels|).
25
+ --- Use either own function or one of the implemented methods:
26
+ --- - Add value to channel or multiply it by coefficient. Like "add 10
27
+ --- to saturation of every color" or "multiply saturation by 2" to
28
+ --- make colors more saturated (less gray).
29
+ --- - Invert. Like "invert lightness" to convert between dark/light theme.
30
+ --- - Set to one or more values (picks closest to current one). Like
31
+ --- "set to one or two hues" to make mono- or dichromatic color scheme.
32
+ --- - Repel from certain source(s) with stronger effect for closer values.
33
+ --- Like "repel from hue 30" to remove red color from color scheme.
34
+ --- Repel hue (how much is removed) is configurable.
35
+ --- - Simulate color vision deficiency.
36
+ ---
37
+ --- - Once color scheme is ready, either apply it to see effects right away or
38
+ --- write it into a Lua file as a fully functioning separate color scheme.
39
+ ---
40
+ --- - Experiment interactively with a feedback (|MiniColors.interactive()|).
41
+ ---
42
+ --- - Animate transition between color schemes either with |MiniColors.animate()|
43
+ --- or with |:Colorscheme| user command.
44
+ ---
45
+ --- - Convert within supported color spaces (|MiniColors.convert()|):
46
+ --- - Hex string.
47
+ --- - 8-bit number (terminal colors).
48
+ --- - RGB.
49
+ --- - Oklab, Oklch, Okhsl (https://bottosson.github.io/posts/oklab/).
50
+ ---
51
+ --- Notes:
52
+ --- - There is a collection of |MiniColors-recipes| with code snippets for some
53
+ --- common tasks.
54
+ --- - There is no goal to support as many color spaces as possible, only the
55
+ --- already present ones.
56
+ ---
57
+ --- # Tweak quick start ~
58
+ ---
59
+ --- - Execute `:lua require('mini.colors').interactive()`.
60
+ ---
61
+ --- - Experiment by writing calls to exposed color scheme methods and applying
62
+ --- them with `<M-a>`. For more information, see |MiniColors-colorscheme-methods|
63
+ --- and |MiniColors-recipes|.
64
+ ---
65
+ --- - If you are happy with result, write color scheme with `<M-w>`. If not,
66
+ --- reset to initial color scheme with `<M-r>`.
67
+ ---
68
+ --- - If only some highlight groups can be made better, adjust them manually
69
+ --- inside written color scheme file.
70
+ ---
71
+ --- # Setup ~
72
+ ---
73
+ --- This module doesn't need setup, but it can be done to improve usability.
74
+ --- Setup with `require('mini.colors').setup({})` (replace `{}` with your
75
+ --- `config` table). It will create global Lua table `MiniColors` which you can
76
+ --- use for scripting or manually (with `:lua MiniColors.*`).
77
+ ---
78
+ --- See |MiniColors.config| for `config` structure and default values.
79
+ ---
80
+ --- This module doesn't have runtime options, so using `vim.b.minicolors_config`
81
+ --- will have no effect here.
82
+ ---
83
+ --- # Comparisons ~
84
+ ---
85
+ --- - [rktjmp/lush.nvim](https://github.com/rktjmp/lush.nvim):
86
+ --- - Oriented towards tweaking separate highlight groups, while |mini.colors|
87
+ --- is more designed to work with color scheme as a whole.
88
+ --- - Uses HSL and HSLuv color spaces, while |mini.colors| uses Oklab, Oklch,
89
+ --- and Okhsl which have slightly better perceptual uniformity properties.
90
+ --- - Doesn't have functionality to infer and repair missing data in color
91
+ --- scheme (like cterm attributes, terminal colors, transparency, etc.),
92
+ --- while |mini.colors| does.
93
+ --- - Doesn't implement animation of color scheme transition, while
94
+ --- |mini.colors| does.
95
+ --- - [lifepillar/vim-colortemplate](https://github.com/lifepillar/vim-colortemplate):
96
+ --- - Comparisons are similar to that of `rktjmp/lush.nvim`.
97
+ --- - [tjdevries/colorbuddy.nvim](https://github.com/tjdevries/colorbuddy.nvim):
98
+ --- - Comparisons are similar to that of `rktjmp/lush.nvim`.
99
+ ---@tag MiniColors
100
+
101
+ --- All following code snippets assume to be executed inside interactive buffer
102
+ --- (|MiniColors.interactive()|). They are directly copy-pasteable.
103
+ ---
104
+ --- To apply single method to current color scheme, use >vim
105
+ --- :lua MiniColors.get_colorscheme():<method goes here>:apply().
106
+ --- <
107
+ --- Recipes:
108
+ --- - Tweak lightness: >lua
109
+ ---
110
+ --- -- Invert dark/light color scheme to be light/dark
111
+ --- chan_invert('lightness', { gamut_clip = 'cusp' })
112
+ ---
113
+ --- -- Ensure constant contrast ratio
114
+ --- chan_set('lightness', 15, { filter = 'bg' })
115
+ --- chan_set('lightness', 85, { filter = 'fg' })
116
+ --- <
117
+ --- - Tweak saturation: >lua
118
+ ---
119
+ --- -- Make background colors less saturated and foreground - more
120
+ --- chan_add('saturation', -20, { filter = 'bg' })
121
+ --- chan_add('saturation', 20, { filter = 'fg' })
122
+ ---
123
+ --- -- Convert to grayscale
124
+ --- chan_set('saturation', 0)
125
+ --- <
126
+ --- - Tweak hue: >lua
127
+ ---
128
+ --- -- Create monochromatic variant (this uses green color)
129
+ --- chan_set('hue', 135)
130
+ ---
131
+ --- -- Create dichromatic variant (this uses Neovim-themed hues)
132
+ --- chan_set('hue', { 140, 245 })
133
+ --- <
134
+ --- - Tweak temperature: >lua
135
+ ---
136
+ --- -- Invert temperature (make cold theme become warm and vice versa)
137
+ --- chan_invert('temperature')
138
+ ---
139
+ --- -- Make background colors colder and foreground warmer
140
+ --- chan_add('temperature', -40, { filter = 'bg' })
141
+ --- chan_add('temperature', 40, { filter = 'fg' })
142
+ --- <
143
+ --- - Counter color vision deficiency (try combinations of these to see which
144
+ --- one works best for you):
145
+ ---
146
+ --- - Improve text saturation contrast (usually the best starting approach): >lua
147
+ ---
148
+ --- chan_set('saturation', { 10, 90 }, { filter = 'fg' })
149
+ --- <
150
+ --- - Remove certain hues from all colors (use 30 for red, 90 for yellow,
151
+ --- 135 for green, 270 for blue): >lua
152
+ ---
153
+ --- -- Repel red color
154
+ --- chan_repel('hue', 30, 45)
155
+ --- <
156
+ --- - Force equally spaced palette (remove ones with which you know you
157
+ --- have trouble): >lua
158
+ ---
159
+ --- -- Might be a good choice for red-green color blindness
160
+ --- chan_set('hue', { 90, 180, 270})
161
+ ---
162
+ --- -- Might be a good choice for blue-yellow color blindness
163
+ --- chan_set('hue', { 0, 90, 180 })
164
+ --- <
165
+ --- - Inverting temperature or pressure can sometimes improve readability: >lua
166
+ ---
167
+ --- chan_invert('temperature')
168
+ --- chan_invert('pressure')
169
+ --- <
170
+ --- - If all hope is lost, hue random generation might help if you are lucky: >lua
171
+ ---
172
+ --- chan_modify('hue', function() return math.random(0, 359) end)
173
+ --- <
174
+ --- - For color scheme creators use |MiniColors-colorscheme:simulate_cvd()| to
175
+ --- simulate various color vision deficiency types to see how color scheme
176
+ --- would look in the eyes of color blind person.
177
+ ---@tag MiniColors-recipes
178
+
179
+ --- Color space is a way to quantitatively describe a color. In this module
180
+ --- color spaces are used both as source for |MiniColors-channels| and inputs
181
+ --- for |MiniColors.convert()|
182
+ ---
183
+ --- List of supported color spaces (along with their id in parenthesis):
184
+ --- - 8-bit (`8-bit`) - integer between 16 and 255. Usually values 0-15 are also
185
+ --- supported, but they depend on terminal emulator theme which is not reliable.
186
+ --- See https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit .
187
+ ---
188
+ --- - Hex (`hex`) - string of the form "#xxxxxx" where `x` is a hexadecimal number.
189
+ ---
190
+ --- - RGB (`rgb`) - table with numeric fields `r` (red), `g` (green), `b` (blue).
191
+ --- Visible range is from 0 to 255.
192
+ ---
193
+ --- - Oklab (`oklab`) - table with fields `l` (lightness; numeric in [0; 100]),
194
+ --- `a`, `b` (both are unbounded numeric; visible range is usually between
195
+ --- -30 to 30). Field `l` describes how light is color; `a` - how "green-red" it is;
196
+ --- `b` - how "blue-yellow" it is.
197
+ ---
198
+ --- - Oklch (`oklch`) - table with fields `l` (same as in Oklab),
199
+ --- `c` (chroma; positive numeric, visible range usually lower than 32),
200
+ --- `h` (`nil` for grays or periodic numeric in [0, 360)). Field `c` describes how
201
+ --- colorful a color is; `h` is a value of "true color" on color circle/wheel.
202
+ --- NOTE: gray colors, being achromatic by nature, don't have hue.
203
+ ---
204
+ --- - Okhsl (`okhsl`) - Oklch but with `c` replaced by `s` (saturation; numeric
205
+ --- in [0; 100]). Field `s` describes a percent of chroma relative to maximum
206
+ --- visible chroma for the particular lightness and hue combination. Note,
207
+ --- that mathematical model used to compute maximum visible chroma is
208
+ --- approximate which might lead to inaccuracies for highly saturated colors
209
+ --- with relatively low or high lightness.
210
+ ---
211
+ --- Sources for Oklab/Oklch/Okhsl:
212
+ --- - https://bottosson.github.io/posts/oklab/ - initial derivation and
213
+ --- introduction of Oklab and Oklch.
214
+ --- - https://bottosson.github.io/misc/colorpicker - interactive color picker.
215
+ --- Great way for a hands-on introduction to concepts of lightness, chroma,
216
+ --- saturation, and hue.
217
+ ---
218
+ --- Note that Oklab/Oklch/Okhsl use channel normalization for `l`, `a`, `b`, `c`, `s` that
219
+ --- is more oriented towards integer numbers (according to the above sources).
220
+ --- Some implementations (like in CSS) are more oriented towards [0; 1] range or
221
+ --- percentages. Adjust accordingly by dividing/multiplying output by 100.
222
+ --- Also use `adjust_lightness = false` in |MiniColors.convert()|.
223
+ ---
224
+ --- # Gamut clip ~
225
+ --- *MiniColors-gamut-clip*
226
+ ---
227
+ --- In Neovim highlight group colors are usually specified by their red, green,
228
+ --- and blue values from 0 to 255 in the form of HEX string (see |gui-colors|).
229
+ --- Although plenty, these are not all possible colors.
230
+ ---
231
+ --- When performing color manipulation using |MiniColors-colorscheme-methods|,
232
+ --- it is possible to end up with "impossible" color (which can't be directly
233
+ --- converted to HEX string). For example, inverting lightness of color "#fce094"
234
+ --- will lead to a color `{ l = 10, c = 10, h = 90 }` in Oklch space, i.e.
235
+ --- "dark yellow" which is impossible to show in HEX.
236
+ ---
237
+ --- GAMUT CLIPPING is an action of converting color outside of visible gamut
238
+ --- (colors representable with HEX string) to be inside it while preserving
239
+ --- certain perceptual characteristics as much as possible.
240
+ ---
241
+ --- Gamut clipping in this module is done inside Oklch color space. The goal is to
242
+ --- preserve hue as much as possible while manipulating lightness and/or chroma.
243
+ ---
244
+ --- List of supported gamut clip methods (along with their id in parenthesis):
245
+ --- - Clip chroma (`'chroma'`) - reduce chroma while preserving lightness until
246
+ --- color is inside visible gamut. Default method.
247
+ ---
248
+ --- - Clip lightness (`'lightness'`) - reduce lightness while preserving chroma
249
+ --- until color is inside visible gamut.
250
+ ---
251
+ --- - Clip according to "cusp" (`'cusp'`) - reduce both lightness and chroma in
252
+ --- a compromise way depending on hue.
253
+ --- Cusp is a color with the highest chroma inside slice of visible gamut
254
+ --- with the same hue (hue leaf). It is called that way because the slice has
255
+ --- a roughly triangular shape with points at (0, 0) - (0, 100) - "cusp" in
256
+ --- (chroma, lightness) coordinates.
257
+ --- Gamut clipping using "cusp" as reference is done by changing color towards
258
+ --- (0, cusp_lightness) point (gray with lightness equal to that of a current
259
+ --- cusp) until color is inside visible gamut.
260
+ ---
261
+ --- In short:
262
+ --- - Usually `'chroma'` is enough.
263
+ --- - If colors are too desaturated - try `'cusp'`.
264
+ --- - If still not colorful enough - try `'lightness'`.
265
+ ---
266
+ --- Notes:
267
+ --- - Currently implemented formulas are approximate (by design; to reduce code
268
+ --- complexity) so there might be problems for highly saturated colors with
269
+ --- relatively low or high lightness.
270
+ ---@tag MiniColors-color-spaces
271
+
272
+ --- A color channel is a number describing one particular aspect of a color.
273
+ --- It is usually direct or modified coordinate of a color space. See
274
+ --- |MiniColors-color-spaces| for information on color spaces.
275
+ ---
276
+ --- List of supported channels (along with their id in parenthesis):
277
+ --- - Lightness (`lightness`) - corrected `l` component of Oklch. Describes how
278
+ --- light is a color. Ranges from 0 (black dark) to 100 (white light).
279
+ ---
280
+ --- - Chroma (`chroma`) - `c` component of Oklch. Describes how colorful is
281
+ --- a color in absolute units. Ranges from 0 (gray) to infinity (more like
282
+ --- around 30 in practice).
283
+ ---
284
+ --- - Saturation (`saturation`) - `s` component of Okhsl. Describes how colorful
285
+ --- is color in relative units. Ranges from 0 (gray) to 100 (maximum saturation
286
+ --- for a given lightness-hue pair).
287
+ ---
288
+ --- - Hue (`hue`) - `h` component of Oklch. Describes "true color value" (like
289
+ --- red/green/blue) as a number. It is a periodic value from 0 (included) to
290
+ --- 360 (not included). Best perceived as a degree on a color circle/wheel.
291
+ ---
292
+ --- Approximate values for common color names:
293
+ --- - 0 - pink.
294
+ --- - 30 - red.
295
+ --- - 60 - orange.
296
+ --- - 90 - yellow.
297
+ --- - 135 - green.
298
+ --- - 180 - cyan.
299
+ --- - 225 - light blue.
300
+ --- - 270 - blue.
301
+ --- - 315 - magenta/purple.
302
+ ---
303
+ --- - Temperature (`temperature`) - circular distance from current hue to hue 270
304
+ --- angle (blue). Ranges from 0 (cool) to 180 (hot) anchored at hues 270 (blue)
305
+ --- and 90 (yellow). Similar to `b` channel but tries to preserve chroma.
306
+ ---
307
+ --- - Pressure (`pressure`) - circular distance from current hue to hue 180.
308
+ --- Ranges from 0 (low; green-ish) to 180 (high; red-ish) anchored at hues
309
+ --- 180 and 0. Similar to `a` channel but tries to preserve chroma.
310
+ --- Not widely used; added to have something similar to temperature.
311
+ ---
312
+ --- - a (`a`) - `a` component of Oklab. Describes how "green-red" a color is.
313
+ --- Can have any value. Negative values are "green-ish", positive - "red-ish".
314
+ ---
315
+ --- - b (`b`) - `b` component of Oklab. Describes how "blue-yellow" a color is.
316
+ --- Can have any value. Negative values are "blue-ish", positive - "yellow-ish".
317
+ ---
318
+ --- - Red (`red`) - `r` component of RGB. Describes how much red a color has.
319
+ --- Ranges from 0 (no red) to 255 (full red).
320
+ ---
321
+ --- - Green (`green`) - `g` component of RGB. Describes how much green a color has.
322
+ --- Ranges from 0 (no green) to 255 (full green).
323
+ ---
324
+ --- - Blue (`blue`) - `b` component of RGB. Describes how much blue a color has.
325
+ --- Ranges from 0 (no blue) to 255 (full blue).
326
+ ---@tag MiniColors-channels
327
+
328
+ ---@alias __colors_channel `(string)` One of supported |MiniColors-channels|.
329
+ ---@alias __colors_chan_opts `(table|nil)` Options. Possible fields:
330
+ --- - <filter> `(function|string)` - filter colors to update. Possible values:
331
+ --- - String representing target attributes. One of `'fg'`, `'bg'`, `'sp'`,
332
+ --- `'term'` (only terminal colors).
333
+ --- - Callable with signature as in |MiniColors-colorscheme:color_modify()|.
334
+ --- Default: `nil` to update all colors.
335
+ --- - <gamut_clip> `(string)` - gamut clipping method. One of `'chroma'`,
336
+ --- `'lightness'`, `'cusp'`. See |MiniColors-gamut-clip|. Default: `'chroma'`.
337
+
338
+ --- Colorscheme object is a central structure of this module. It contains all
339
+ --- data relevant to colors in fields and provides methods to modify it.
340
+ ---
341
+ --- Create colorscheme object manually with |MiniColors.as_colorscheme()|: >lua
342
+ ---
343
+ --- MiniColors.as_colorscheme({
344
+ --- name = 'my_cs',
345
+ --- groups = {
346
+ --- Normal = { fg = '#dddddd', bg = '#222222' },
347
+ --- SpellBad = { sp = '#dd2222', undercurl = true },
348
+ --- },
349
+ --- terminal = { [0] = '#222222', [1] = '#dd2222' },
350
+ --- })
351
+ --- <
352
+ --- Get any registered color scheme (including currently active) as colorscheme
353
+ --- object with |MiniColors.get_colorscheme()|: >lua
354
+ ---
355
+ --- -- Get current color scheme
356
+ --- MiniColors.get_colorscheme()
357
+ ---
358
+ --- -- Get registered color scheme by name
359
+ --- MiniColors.get_colorscheme('minischeme', { new_name = 'maxischeme' })
360
+ --- <
361
+ ---@class Colorscheme
362
+ ---
363
+ --- *MiniColors-colorscheme-fields*
364
+ ---
365
+ ---@field name string|nil Name of the color scheme (as in |g:colors_name|).
366
+ ---
367
+ ---@field groups table|nil Table with highlight groups data. Keys are group
368
+ --- names appropriate for `name` argument of |nvim_set_hl()|, values - tables
369
+ --- appropriate for its `val` argument. Note: gui colors are accepted only in
370
+ --- short form (`fg`, `bg`, `sp`).
371
+ ---
372
+ ---@field terminal table|nil Table with terminal colors data (|terminal-config|).
373
+ --- Keys are numbers from 0 to 15, values - strings representing color (hex
374
+ --- string or plain color name; see |nvim_get_color_by_name()|).
375
+ ---
376
+ --- # Methods ~
377
+ --- *MiniColors-colorscheme-methods*
378
+ ---
379
+ --- Notes about all methods:
380
+ --- - They never modify underlying colorscheme object instead returning deep
381
+ --- copy with modified fields.
382
+ --- - They accept `self` colorscheme object as first argument meaning they should be
383
+ --- called with `:` notation (like `cs:method()`).
384
+ ---
385
+ --- Example calling methods: >lua
386
+ ---
387
+ --- -- Get current color scheme, set hue of colors to 135, infer cterm
388
+ --- -- attributes and apply
389
+ --- local cs = MiniColors.get_colorscheme()
390
+ --- cs:chan_set('hue', 135):add_cterm_attributes():apply()
391
+ --- <
392
+ --- ## add_cterm_attributes() ~
393
+ --- *MiniColors-colorscheme:add_cterm_attributes()*
394
+ ---
395
+ --- Infer |cterm-colors| based on present |gui-colors|. It updates `ctermbg`/`ctermfg`
396
+ --- based on `fg`/`bg` by approximating in perceptually uniform distance in Oklab
397
+ --- space (|MiniColors-color-spaces|).
398
+ ---
399
+ --- ### Parameters ~
400
+ --- {opts} `(table|nil)` Options. Possible fields:
401
+ --- - <force> `(boolean)` - Whether to replace already present cterm attributes
402
+ --- with inferred ones. Default: `true`.
403
+ ---
404
+ --- ## add_terminal_colors() ~
405
+ --- *MiniColors-colorscheme:add_terminal_colors()*
406
+ ---
407
+ --- Infer terminal colors (|terminal-config|) based on colorscheme palette
408
+ --- (see |MiniColors-colorscheme:get_palette()|). It updates `terminal` field
409
+ --- based on color scheme's palette by picking the most appropriate entry to
410
+ --- represent terminal color. Colors from 0 to 7 are attempted to be black,
411
+ --- red, green, yellow, blue, magenta, cyan, white. Colors from 8 to 15 are
412
+ --- the same as from 0 to 7.
413
+ ---
414
+ --- ### Parameters ~
415
+ --- {opts} `(table|nil)` Options. Possible fields:
416
+ --- - <force> `(boolean)` - Whether to replace already present terminal colors
417
+ --- with inferred ones. Default: `true`.
418
+ --- - <palette_args> `(table)` - |MiniColors-colorscheme:get_palette()| arguments.
419
+ ---
420
+ --- ## add_transparency() ~
421
+ --- *MiniColors-colorscheme:add_transparency()*
422
+ ---
423
+ --- Add transparency by removing background from a certain highlight groups.
424
+ --- Requires actual transparency from terminal emulator to see background image.
425
+ --- Has no effect on linked groups; use |MiniColors-colorscheme:resolve_links()|
426
+ --- explicitly before applying transparency.
427
+ ---
428
+ --- ### Parameters ~
429
+ --- {opts} `(table|nil)` Options. Possible fields can be used to configure which
430
+ --- sets of highlight groups to update:
431
+ --- - <general> `(boolean)` - general groups (like `Normal`). Default: `true`.
432
+ --- - <float> `(boolean)` - built-in groups for floating windows. Default: `false`.
433
+ --- - <statuscolumn> `(boolean)` - groups related to |'statuscolumn'| (signcolumn,
434
+ --- numbercolumn, foldcolumn, `DiagnosticSignXxx`, and `XxxMsg` groups). Also
435
+ --- updates groups for all currently defined signs. Default: `false`.
436
+ --- - <statusline> `(boolean)` - built-in groups for |'statusline'|. Default: `false`.
437
+ --- - <tabline> `(boolean)` - built-in groups for |'tabline'|. Default: `false`.
438
+ --- - <winbar> `(boolean)` - built-in groups for |'winbar'|. Default: `false`.
439
+ ---
440
+ --- ## apply() ~
441
+ --- *MiniColors-colorscheme:apply()*
442
+ ---
443
+ --- Apply colorscheme:
444
+ --- - Set |g:colors_name| to a `name` field.
445
+ --- - Apply highlight groups in a `groups` field.
446
+ --- - Set terminal colors from a `terminal` field.
447
+ ---
448
+ --- ### Parameters ~
449
+ --- {opts} `(table|nil)` Options. Possible fields:
450
+ --- - <clear> `(boolean)` - whether to execute |:hi-clear| first. Default: `true`.
451
+ ---
452
+ --- ## chan_add() ~
453
+ --- *MiniColors-colorscheme:chan_add()*
454
+ ---
455
+ --- Add value to a channel (see |MiniColors-channels|).
456
+ ---
457
+ --- ### Parameters ~
458
+ --- {channel} __colors_channel
459
+ --- {value} `(number)` Number to add (can be negative).
460
+ --- {opts} __colors_chan_opts
461
+ ---
462
+ ---
463
+ --- ## chan_invert() ~
464
+ --- *MiniColors-colorscheme:chan_invert()*
465
+ ---
466
+ --- Invert value in a channel (see |MiniColors-channels|).
467
+ ---
468
+ --- Notes:
469
+ --- - Most Oklab/Oklch inversions are not exactly invertible: applying it twice
470
+ --- might lead to slightly different colors depending on gamut clip method
471
+ --- (|MiniColors-gamut-clip|) like smaller chroma with default `'chroma'` method.
472
+ ---
473
+ --- ### Parameters ~
474
+ --- {channel} __colors_channel
475
+ --- {opts} __colors_chan_opts
476
+ ---
477
+ --- ## chan_modify() ~
478
+ --- *MiniColors-colorscheme:chan_modify()*
479
+ ---
480
+ --- Modify channel with a callable.
481
+ ---
482
+ --- ### Parameters ~
483
+ --- {channel} __colors_channel
484
+ --- {f} `(function)` - callable which defines modification. Should take current
485
+ --- value of a channel and return a new one.
486
+ --- {opts} __colors_chan_opts
487
+ ---
488
+ --- ## chan_multiply() ~
489
+ --- *MiniColors-colorscheme:chan_multiply()*
490
+ ---
491
+ --- Multiply value of a channel (see |MiniColors-channels|).
492
+ ---
493
+ --- ### Parameters ~
494
+ --- {channel} __colors_channel
495
+ --- {coef} `(number)` Number to multiply with (can be negative).
496
+ --- {opts} __colors_chan_opts
497
+ ---
498
+ --- ## chan_repel() ~
499
+ --- *MiniColors-colorscheme:chan_repel()*
500
+ ---
501
+ --- Repel from certain sources.
502
+ ---
503
+ --- Given an array of repel centers (`sources`) and repel degree (`coef`) add to
504
+ --- current channel value some amount ("nudge") with the following properties:
505
+ --- - Nudges from several sources are added together.
506
+ --- - Nudge is directly proportional to `coef`: bigger `coef` means bigger nudge.
507
+ --- - Nudge is inversely proportional to the distance between current value and
508
+ --- source: for positive `coef` bigger distance means smaller nudge, i.e.
509
+ --- repel effect weakens with distance.
510
+ --- - With positive `coef` nudges close to source are computed in a way to remove
511
+ --- whole `[source - coef; source + coef]` range.
512
+ --- - Negative `coef` results into attraction to source. Nudges in
513
+ --- `[source - coef; source + coef]` range are computed to completely collapse it
514
+ --- into `source`.
515
+ ---
516
+ --- Examples: >lua
517
+ ---
518
+ --- -- Repel hue from red color removing hue in range from 20 to 40
519
+ --- chan_repel('hue', 30, 10)
520
+ ---
521
+ --- -- Attract hue to red color collapsing [20; 40] range into 30.
522
+ --- chan_repel('hue', 30, -10)
523
+ --- <
524
+ --- ### Parameters ~
525
+ --- {channel} __colors_channel
526
+ --- {sources} `(table|number)` Single or multiple source from which to repel.
527
+ --- {coef} `(number)` Repel degree (can be negative to attract).
528
+ --- {opts} __colors_chan_opts
529
+ ---
530
+ --- ## chan_set() ~
531
+ --- *MiniColors-colorscheme:chan_set()*
532
+ ---
533
+ --- Set channel to certain value(s). This can be used to ensure that channel has
534
+ --- value(s) only within supplied set. If more than one is supplied, closest
535
+ --- element to current value is used.
536
+ ---
537
+ --- ### Parameters ~
538
+ --- {channel} __colors_channel
539
+ --- {values} `(table|number)` Single or multiple values to set.
540
+ --- {opts} __colors_chan_opts
541
+ ---
542
+ --- ## color_modify() ~
543
+ --- *MiniColors-colorscheme:color_modify()*
544
+ ---
545
+ --- Modify all colors with a callable. It should return new color value (hex
546
+ --- string or `nil` to remove attribute) base on the following input:
547
+ --- - Current color as hex string.
548
+ --- - Data about the color: a table with fields:
549
+ --- - <attr> - one of `'fg'`, `'bg'`, `'sp'`, and `'term'` for terminal color.
550
+ --- - <name> - name of color source. Either a name of highlight group or
551
+ --- string of the form `terminal_color_x` for terminal color (as in
552
+ --- |terminal-config|).
553
+ ---
554
+ --- Example: >lua
555
+ ---
556
+ --- -- Set to '#dd2222' all foreground colors for groups starting with "N"
557
+ --- color_modify(function(hex, data)
558
+ --- if data.attr == 'fg' and data.name:find('^N') then
559
+ --- return '#dd2222'
560
+ --- end
561
+ --- return hex
562
+ --- end)
563
+ --- <
564
+ --- ### Parameters ~
565
+ --- {f} `(function)` Callable returning new color value.
566
+ ---
567
+ --- ## compress() ~
568
+ --- *MiniColors-colorscheme:compress()*
569
+ ---
570
+ --- Remove redundant highlight groups. These are one of the two kinds:
571
+ --- - Having values identical to ones after |:hi-clear| (meaning they usually
572
+ --- don't add new information).
573
+ --- - Coming from a curated list of plugins with highlight groups usually not
574
+ --- worth keeping around. Current list of such plugins:
575
+ --- - [nvim-tree/nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons)
576
+ --- - [norcalli/nvim-colorizer.lua](https://github.com/norcalli/nvim-colorizer.lua)
577
+ ---
578
+ --- This method is useful to reduce size of color scheme before writing into
579
+ --- the file with |MiniColors-colorscheme:write()|.
580
+ ---
581
+ --- ### Parameters ~
582
+ --- {opts} `(table|nil)` Options. Possible fields:
583
+ --- - <plugins> `(boolean)` - whether to remove highlight groups from a curated
584
+ --- list of plugins. Default: `true`.
585
+ ---
586
+ --- ## get_palette() ~
587
+ --- *MiniColors-colorscheme:get_palette()*
588
+ ---
589
+ --- Get commonly used colors. This basically counts number of all color
590
+ --- occurrences and filter out rare ones.
591
+ ---
592
+ --- It is usually a good idea to apply both |MiniColors-colorscheme:compress()|
593
+ --- and |MiniColors-colorscheme:resolve_links()| before applying this.
594
+ ---
595
+ --- ### Parameters ~
596
+ --- {opts} `(table|nil)` Options. Possible fields:
597
+ --- - <threshold> `(number)` - relative threshold for groups to keep. A group
598
+ --- is not included in output if it has less than this many occurrences
599
+ --- relative to a total number of colors. Default: 0.01.
600
+ ---
601
+ --- ## resolve_links() ~
602
+ --- *MiniColors-colorscheme:resolve_links()*
603
+ ---
604
+ --- Resolve links (|:highlight-link|). This makes all highlight groups with `link`
605
+ --- attribute have data from a linked one.
606
+ ---
607
+ --- Notes:
608
+ --- - Resolves nested links.
609
+ --- - If some group is linked to a group missing in current colorscheme object,
610
+ --- it is not resolved.
611
+ ---
612
+ --- ## simulate_cvd() ~
613
+ --- *MiniColors-colorscheme:simulate_cvd()*
614
+ ---
615
+ --- Simulate color vision deficiency (CVD, color blindness). This is basically
616
+ --- a wrapper using |MiniColors.simulate_cvd()| as a part of
617
+ --- call to |MiniColors-colorscheme:color_modify()| method.
618
+ ---
619
+ --- ### Parameters ~
620
+ --- {cvd_type} `(string)` One of `'protan'`, `'deutan'`, `'tritan'`, `'mono'`.
621
+ --- {severity} `(number|nil)` Severity of CVD, number between 0 and 1. Default: 1.
622
+ ---
623
+ --- ## write() ~
624
+ --- *MiniColors-colorscheme:write()*
625
+ ---
626
+ --- Write color scheme to a file. It will be a Lua script readily usable as
627
+ --- a regular color scheme. Useful to both save results of color scheme tweaking
628
+ --- and making local snapshot of some other color scheme.
629
+ ---
630
+ --- Sourcing this file on startup usually leads to a better performance that
631
+ --- sourcing initial color scheme, as it is essentially a conditioned
632
+ --- |:hi-clear| call followed by a series of |nvim_set_hl()| calls.
633
+ ---
634
+ --- Default writing location is a "colors" directory of your Neovim config
635
+ --- directory (see |base-directories|). After writing, it should be available
636
+ --- for sourcing with |:colorscheme| or |:Colorscheme|.
637
+ ---
638
+ --- Name of the file by default is taken from `name` field (`'mini_colors'` is
639
+ --- used if it is `nil`). If color scheme with this name already exists, it
640
+ --- appends prefix based on current time to make it unique.
641
+ ---
642
+ --- Notes:
643
+ --- - If colors were updated, it is usually a good idea to infer cterm attributes
644
+ --- with |MiniColors-colorscheme:add_cterm_attributes()| prior to writing.
645
+ ---
646
+ --- ### Parameters ~
647
+ --- {opts} `(table|nil)` Options. Possible fields:
648
+ --- - <compress> `(boolean)` - whether to call |MiniColors-colorscheme:compress()|
649
+ --- prior to writing. Default: `true`.
650
+ --- - <name> `(string|nil)` - basename of written file. Default: `nil` to infer
651
+ --- from `name` field.
652
+ --- - <directory> `(string)` - directory to where file should be saved.
653
+ --- Default: "colors" subdirectory of Neovim home config (`stdpath("config")`).
654
+ ---@tag MiniColors-colorscheme
655
+
656
+ ---@diagnostic disable:undefined-field
657
+ ---@diagnostic disable:discard-returns
658
+ ---@diagnostic disable:unused-local
659
+
660
+ -- Module definition ==========================================================
661
+ local MiniColors = {}
662
+ local H = {}
663
+
664
+ --- Module setup
665
+ ---
666
+ --- # :Colorscheme ~
667
+ --- *:Colorscheme*
668
+ ---
669
+ --- Calling this function creates a `:Colorscheme` user command. It takes one or
670
+ --- more registered color scheme names and performs animated transition between
671
+ --- them (starting from currently active color scheme).
672
+ --- It uses |MiniColors.animate()| with default options.
673
+ ---
674
+ ---@param config table|nil Module config table. See |MiniColors.config|.
675
+ ---
676
+ ---@usage >lua
677
+ --- require('mini.colors').setup() -- use default config
678
+ --- -- OR
679
+ --- require('mini.colors').setup({}) -- replace {} with your config table
680
+ --- <
681
+ MiniColors.setup = function(config)
682
+ -- TODO: Remove after Neovim=0.9 support is dropped
683
+ if vim.fn.has('nvim-0.10') == 0 then
684
+ vim.notify(
685
+ '(mini.colors) Neovim<0.10 is soft deprecated (module works but is not supported).'
686
+ .. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
687
+ .. ' Please update your Neovim version.'
688
+ )
689
+ end
690
+
691
+ -- Export module
692
+ _G.MiniColors = MiniColors
693
+
694
+ -- Setup config
695
+ config = H.setup_config(config)
696
+
697
+ -- Apply config
698
+ H.apply_config(config)
699
+
700
+ -- Create user commands
701
+ H.create_user_commands()
702
+ end
703
+
704
+ --- Defaults ~
705
+ ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
706
+ MiniColors.config = {}
707
+ --minidoc_afterlines_end
708
+
709
+ --- Create colorscheme object
710
+ ---
711
+ ---@param x table Table to be transformed into |MiniColors-colorscheme| object.
712
+ ---
713
+ ---@return table Copy of `x` transformed into a colorscheme object.
714
+ MiniColors.as_colorscheme = function(x)
715
+ -- Validate input
716
+ if not H.is_table(x) then H.error('Input of `as_colorscheme()` should be table.') end
717
+
718
+ if x.groups ~= nil then
719
+ if not H.is_table(x.groups) then H.error('Field `groups` of colorscheme should be table or nil.') end
720
+ if not H.all(x.groups, H.is_table) then H.error('All elements of `groups` colorscheme field should be tables.') end
721
+ end
722
+
723
+ if x.terminal ~= nil then
724
+ if not H.is_table(x.terminal) then H.error('Field `terminal` of colorscheme should be table or nil.') end
725
+ if not H.all(x.terminal, H.is_string) then
726
+ H.error('All elements of `terminal` colorscheme field should be strings.')
727
+ end
728
+ end
729
+
730
+ -- Create a proper copy
731
+ local res = vim.deepcopy(x)
732
+
733
+ -- - Ensure that tables for highlight groups are independent (can not be the
734
+ -- case if some point to literally the same table)
735
+ if H.is_table(res.groups) then
736
+ for key, val in pairs(res.groups) do
737
+ res.groups[key] = vim.deepcopy(val)
738
+ end
739
+ end
740
+
741
+ -- Fields
742
+ res.groups = res.groups or {}
743
+ res.name = res.name
744
+ res.terminal = res.terminal or {}
745
+
746
+ -- Methods
747
+ res.add_cterm_attributes = H.cs_add_cterm_attributes
748
+ res.add_terminal_colors = H.cs_add_terminal_colors
749
+ res.add_transparency = H.cs_add_transparency
750
+ res.apply = H.cs_apply
751
+ res.chan_add = H.cs_chan_add
752
+ res.chan_invert = H.cs_chan_invert
753
+ res.chan_modify = H.cs_chan_modify
754
+ res.chan_multiply = H.cs_chan_multiply
755
+ res.chan_repel = H.cs_chan_repel
756
+ res.chan_set = H.cs_chan_set
757
+ res.color_modify = H.cs_color_modify
758
+ res.compress = H.cs_compress
759
+ res.get_palette = H.cs_get_palette
760
+ res.resolve_links = H.cs_resolve_links
761
+ res.simulate_cvd = H.cs_simulate_cvd
762
+ res.write = H.cs_write
763
+
764
+ return res
765
+ end
766
+
767
+ --- Get colorscheme object from registered color scheme
768
+ ---
769
+ ---@param name string|nil Name of color scheme to use. If `nil` (default) creates
770
+ --- colorscheme object based on currently active data (|g:colors_name|,
771
+ --- highlight groups, terminal colors). If string, converts color scheme with
772
+ --- that name to a colorscheme object.
773
+ ---@param opts table|nil Options. Possible fields:
774
+ --- - <new_name> `(string|nil)` - new name of colorscheme object.
775
+ ---
776
+ ---@return table Colorscheme object (|MiniColors-colorscheme|).
777
+ MiniColors.get_colorscheme = function(name, opts)
778
+ if not (name == nil or type(name) == 'string') then H.error('Argument `name` should be string or `nil`.') end
779
+ opts = vim.tbl_deep_extend('force', { new_name = nil }, opts or {})
780
+
781
+ -- Return current color scheme if no `name` is supplied
782
+ if name == nil then
783
+ return MiniColors.as_colorscheme({
784
+ name = opts.new_name or vim.g.colors_name,
785
+ groups = H.get_current_groups(),
786
+ terminal = H.get_current_terminal(),
787
+ })
788
+ end
789
+
790
+ -- Source supplied color scheme, collect it and return back
791
+ local current_cs = MiniColors.get_colorscheme()
792
+ local res, au_id
793
+ au_id = vim.api.nvim_create_autocmd('ColorScheme', {
794
+ callback = function()
795
+ res = MiniColors.get_colorscheme(nil, opts)
796
+ -- Apply right now to avoid flickering
797
+ current_cs:apply()
798
+ -- Explicitly delete autocommand to account for error in `:colorscheme`
799
+ vim.api.nvim_del_autocmd(au_id)
800
+ end,
801
+ })
802
+ local ok, _ = pcall(vim.cmd, 'colorscheme ' .. name)
803
+ if not ok then H.error(string.format('No color scheme named "%s".', name)) end
804
+
805
+ return res
806
+ end
807
+
808
+ --- Start interactive experiments
809
+ ---
810
+ --- Create a special buffer in which user can write plain Lua code to tweak
811
+ --- color scheme and apply to get visual feedback.
812
+ ---
813
+ --- # General principles ~
814
+ --- - Initial colorscheme object is fixed to interactive buffer on its creation.
815
+ ---
816
+ --- - There are special buffer convenience mappings:
817
+ --- - Apply (source) current buffer content.
818
+ --- - Reset color scheme (make initial colorscheme the current one).
819
+ --- - Write to a file the result of applying current buffer content.
820
+ --- This sources current content and calls |MiniColors-colorscheme:write()|.
821
+ --- - Quit interactive buffer.
822
+ ---
823
+ --- - User is expected to iteratively tweak color scheme by writing general Lua
824
+ --- code in interactive buffer and applying it using convenience mapping.
825
+ ---
826
+ --- - Application of interactive buffer is essentially these steps:
827
+ --- - Expose `self` as initial colorscheme object on any application.
828
+ --- It is always the same for every application.
829
+ --- - Expose initial colorscheme methods as standalone functions. So instead
830
+ --- of writing `self = self:add_transparency()` user can only write
831
+ --- `add_transparency()`.
832
+ --- - Source buffer content as plain Lua code.
833
+ ---
834
+ --- Example of interactive buffer content: >lua
835
+ ---
836
+ --- chan_modify('hue', function() return math.random(0, 359) end)
837
+ --- simulate_cvd('protan')
838
+ --- add_cterm_attributes()
839
+ --- add_terminal_colors()
840
+ --- <
841
+ ---@param opts table|nil Options. Possible fields:
842
+ --- - <colorscheme> `(table|nil)` - |MiniColors-colorscheme| object to be
843
+ --- used as initial colorscheme for executed code. By default uses current
844
+ --- color scheme.
845
+ --- - <mappings> `table` - buffer mappings for actions. Possible fields:
846
+ --- - <Apply> `(string)` - apply buffer code. Default: `'<M-a>'`.
847
+ --- - <Reset> `(string)` - apply initial color scheme as is. Default: `'<M-r>'`.
848
+ --- - <Quit> `(string)` - close interactive buffer. Default: `'<M-q>'`.
849
+ --- - <Write> `(string)` - write result of buffer code into a file.
850
+ --- Prompts for file name with |vim.ui.input()| and then
851
+ --- uses |MiniColors-colorscheme:write()| with other options being default.
852
+ --- Default: `'<M-w>'`.
853
+ MiniColors.interactive = function(opts)
854
+ opts = vim.tbl_deep_extend(
855
+ 'force',
856
+ { colorscheme = nil, mappings = { Apply = '<M-a>', Reset = '<M-r>', Quit = '<M-q>', Write = '<M-w>' } },
857
+ opts or {}
858
+ )
859
+ local maps = opts.mappings
860
+
861
+ -- Prepare
862
+ local init_cs = opts.colorscheme == nil and MiniColors.get_colorscheme()
863
+ or MiniColors.as_colorscheme(opts.colorscheme)
864
+ local buf_id = vim.api.nvim_create_buf(true, true)
865
+ H.set_buf_name(buf_id, 'interactive')
866
+
867
+ -- Write header lines
868
+ local header_lines = {
869
+ [[-- Experiment with color scheme using 'mini.colors']],
870
+ '--',
871
+ '-- Treat this as regular Lua file',
872
+ '-- Methods of initial color scheme can be called directly',
873
+ '-- See more in `:h MiniColors.interactive()`',
874
+ '--',
875
+ '-- Initial color scheme: ' .. (init_cs.name or '<unnamed>'),
876
+ '-- Buffer-local mappings (Normal mode):',
877
+ '-- Apply: ' .. maps.Apply,
878
+ '-- Reset: ' .. maps.Reset,
879
+ '-- Quit: ' .. maps.Quit,
880
+ '-- Write: ' .. maps.Write,
881
+ '--',
882
+ '-- Examples:',
883
+ '--',
884
+ '-- Invert dark/light color scheme to be light/dark',
885
+ "-- chan_invert('lightness', { gamut_clip = 'cusp' })",
886
+ '--',
887
+ '-- Make foreground text more saturated',
888
+ "-- chan_add('saturation', 20, { filter = 'fg' })",
889
+ '',
890
+ '',
891
+ }
892
+ vim.api.nvim_buf_set_lines(buf_id, 0, -1, true, header_lines)
893
+
894
+ -- Make local mappings
895
+ local m = function(action, rhs) vim.keymap.set('n', maps[action], rhs, { desc = action, buffer = buf_id }) end
896
+
897
+ m('Apply', function()
898
+ local new_cs = H.apply_interactive_buffer(buf_id, init_cs)
899
+ new_cs:apply()
900
+ end)
901
+ m('Reset', function() init_cs:apply() end)
902
+ m('Quit', function()
903
+ local ok, bufremove = pcall(require, 'mini.bufremove')
904
+ if ok then
905
+ bufremove.wipeout(buf_id, true)
906
+ else
907
+ vim.api.nvim_buf_delete(buf_id, { force = true })
908
+ end
909
+ end)
910
+ m('Write', function()
911
+ vim.ui.input(
912
+ { prompt = [[Write to 'colors/' of your config under this name: ]], default = init_cs.name },
913
+ function(input)
914
+ if input == nil then return end
915
+ local new_cs = H.apply_interactive_buffer(buf_id, init_cs)
916
+ new_cs.name = input
917
+ new_cs:write({ name = input })
918
+ end
919
+ )
920
+ end)
921
+
922
+ -- Set local options
923
+ vim.bo[buf_id].filetype = 'lua'
924
+
925
+ -- Make current
926
+ vim.api.nvim_set_current_buf(buf_id)
927
+ vim.api.nvim_win_set_cursor(0, { vim.api.nvim_buf_line_count(buf_id), 0 })
928
+ end
929
+
930
+ --- Animate color scheme change
931
+ ---
932
+ --- Start from currently active color scheme and loop through `cs_array`.
933
+ ---
934
+ --- Powers |:Colorscheme| user command created in |MiniColors.setup()|.
935
+ ---
936
+ ---@param cs_array table Array of |MiniColors-colorscheme| objects.
937
+ ---@param opts table|nil Options. Possible fields:
938
+ --- - <transition_steps> `(number)` - number of intermediate steps to show
939
+ --- during transition between two color schemes. Bigger values result in
940
+ --- smoother visual feedback but require more computational power.
941
+ --- Default: 25.
942
+ --- - <transition_duration> `(number)` - number of milliseconds to spend
943
+ --- showing transition. Default: 1000.
944
+ --- - <show_duration> `(number)` - number of milliseconds to show intermediate
945
+ --- color schemes (all but last in `cs_array`). Default: 1000.
946
+ MiniColors.animate = function(cs_array, opts)
947
+ if not (H.islist(cs_array) and H.all(cs_array, H.is_colorscheme)) then
948
+ H.error('Argument `cs_array` should be an array of color schemes.')
949
+ end
950
+ opts = vim.tbl_deep_extend(
951
+ 'force',
952
+ { transition_steps = 25, transition_duration = 1000, show_duration = 1000 },
953
+ opts or {}
954
+ )
955
+
956
+ if #cs_array == 0 then return end
957
+
958
+ -- Pre-compute common data
959
+ local cs_oklab = vim.tbl_map(function(cs) return H.cs_hex_to_oklab(vim.deepcopy(cs)) end, cs_array)
960
+ local cs_oklab_current = H.cs_hex_to_oklab(MiniColors.get_colorscheme())
961
+
962
+ -- Make "chain after action" which animates transitions one by one
963
+ local cs_id, after_action = 1, nil
964
+ after_action = function(data)
965
+ -- Ensure authentic color scheme is active
966
+ cs_array[cs_id]:apply()
967
+
968
+ -- Advance if possible
969
+ cs_id = cs_id + 1
970
+ if #cs_array < cs_id then return end
971
+
972
+ -- Wait before starting another animation
973
+ local callback = function() H.animate_single_transition(cs_oklab[cs_id - 1], cs_oklab[cs_id], after_action, opts) end
974
+
975
+ vim.defer_fn(callback, opts.show_duration)
976
+ end
977
+
978
+ H.animate_single_transition(cs_oklab_current, cs_oklab[1], after_action, opts)
979
+ end
980
+
981
+ --- Convert between color spaces
982
+ ---
983
+ --- For a list of supported colors spaces see |MiniColors-color-spaces|.
984
+ ---
985
+ ---@param x table|string|number|nil Color to convert from. Its color space is
986
+ --- inferred automatically.
987
+ ---@param to_space string Id of allowed color space.
988
+ ---@param opts table|nil Options. Possible fields:
989
+ --- - <adjust_lightness> `(boolean)` - whether to adjust lightness value to have
990
+ --- a more uniform progression from 0 to 100. Set `false` for results more
991
+ --- compatible with some other Oklab/Oklch implementations (like in CSS).
992
+ --- Source: "Intermission - a new lightness estimate for Oklab" section of
993
+ --- https://bottosson.github.io/posts/colorpicker
994
+ --- Default: `true`.
995
+ --- - <gamut_clip> `(string)` - method for |MiniColors-gamut-clip|.
996
+ --- Default: `'chroma'`.
997
+ ---
998
+ ---@return table|string|number|nil Color in space `to_space` or `nil` if input is `nil`.
999
+ MiniColors.convert = function(x, to_space, opts)
1000
+ if x == nil then return nil end
1001
+ if not vim.tbl_contains(H.allowed_spaces, to_space) then
1002
+ local spaces = table.concat(vim.tbl_map(vim.inspect, H.allowed_spaces), ', ')
1003
+ H.error('Argument `to_space` should be one of ' .. spaces .. '.')
1004
+ end
1005
+ opts = vim.tbl_deep_extend('force', { adjust_lightness = true, gamut_clip = 'chroma' }, opts or {})
1006
+
1007
+ -- Set reference value here once to not have to pass it as argument to many
1008
+ -- downstream places
1009
+ H.adjust_lightness = opts.adjust_lightness
1010
+ return H.converters[to_space](x, H.infer_color_space(x), opts)
1011
+ end
1012
+
1013
+ --- Modify channel
1014
+ ---
1015
+ ---@param x table|string|number|nil Color which channel will be modified. Color
1016
+ --- space is inferred automatically.
1017
+ ---@param channel string One of supported |MiniColors-channels|.
1018
+ ---@param f function Callable which defines modification. Should take current
1019
+ --- value of a channel and return a new one.
1020
+ ---@param opts table|nil Options. Possible fields:
1021
+ --- - <gamut_clip> `(string)` - method for |MiniColors-gamut-clip|.
1022
+ --- Default: `'chroma'`.
1023
+ ---
1024
+ ---@return string|nil Hex string of color with modified channel or `nil` if input is `nil`.
1025
+ MiniColors.modify_channel = function(x, channel, f, opts)
1026
+ channel = H.normalize_channel(channel)
1027
+ f = H.normalize_f(f)
1028
+ opts = vim.tbl_deep_extend('force', { gamut_clip = 'chroma' }, opts or {})
1029
+
1030
+ local modify_channel = H.channel_modifiers[channel]
1031
+ return modify_channel(x, f, opts.gamut_clip)
1032
+ end
1033
+
1034
+ --- Simulate color vision deficiency
1035
+ ---
1036
+ ---@param x table|string|number|nil Color to convert from. Its color space is
1037
+ --- inferred automatically.
1038
+ ---@param cvd_type string Type of CVD. One of `'protan'`, `'deutan'`,
1039
+ ---`'tritan'`, or `'mono'` (equivalent to converting to graysacle).
1040
+ ---@param severity number|nil Severity of CVD. A number between 0 and 1 (default).
1041
+ ---
1042
+ ---@return string|nil Hex string of simulated color or `nil` if input is `nil`.
1043
+ MiniColors.simulate_cvd = function(x, cvd_type, severity)
1044
+ if x == nil then return nil end
1045
+ if not (cvd_type == 'protan' or cvd_type == 'deutan' or cvd_type == 'tritan' or cvd_type == 'mono') then
1046
+ H.error('Argument `cvd_type` should be one of "protan", "deutan", "tritan", "mono".')
1047
+ end
1048
+ severity = severity or 1
1049
+ if not H.is_number(severity) then H.error('Argument `severity` should be number.') end
1050
+
1051
+ -- Simulate monochromacy by setting zero 'crhoma'
1052
+ if cvd_type == 'mono' then
1053
+ local lch = MiniColors.convert(x, 'oklch')
1054
+ lch.c, lch.h = 0, nil
1055
+ ---@diagnostic disable:return-type-mismatch
1056
+ return MiniColors.convert(lch, 'hex')
1057
+ end
1058
+
1059
+ -- Simulate regular CVD by multiplying with appropriate matrix
1060
+ severity = H.clip(H.round(10 * severity), 0, 10)
1061
+ local mat = H.cvd_matrices[cvd_type][severity]
1062
+ local rgb = MiniColors.convert(x, 'rgb')
1063
+ local new_rgb = {
1064
+ r = mat[1][1] * rgb.r + mat[1][2] * rgb.g + mat[1][3] * rgb.b,
1065
+ g = mat[2][1] * rgb.r + mat[2][2] * rgb.g + mat[2][3] * rgb.b,
1066
+ b = mat[3][1] * rgb.r + mat[3][2] * rgb.g + mat[3][3] * rgb.b,
1067
+ }
1068
+
1069
+ return H.rgb2hex(new_rgb)
1070
+ end
1071
+
1072
+ -- Helper data ================================================================
1073
+ -- Module default config
1074
+ H.default_config = vim.deepcopy(MiniColors.config)
1075
+
1076
+ -- Color conversion constants
1077
+ H.tau = 2 * math.pi
1078
+
1079
+ -- Cusps for Oklch color space. These represent (c, l) points of Oklch space
1080
+ -- (with **not corrected lightness**) inside a hue leaf (points with
1081
+ -- `math.floor(h) = <index>`) with the highest value of chroma (`c`).
1082
+ -- They are used to model the whole RGB gamut (region inside which sRGB colors
1083
+ -- are converted in Oklch space). It is modelled as triangle with vertices:
1084
+ -- (0, 0), (0, 100) and cusp. NOTE: this is an approximation, i.e. not all RGB
1085
+ -- colors lie inside this triangle **AND** not all points inside triangle are
1086
+ -- RGB colors. But both proportions are small: around 0.5% with similar modeled
1087
+ -- RGB color for first one and around 2.16% for second one.
1088
+ --stylua: ignore start
1089
+ ---@diagnostic disable start
1090
+ ---@private
1091
+ H.cusps = {
1092
+ [0] = {26.23,64.74},
1093
+ {26.14,64.65},{26.06,64.56},{25.98,64.48},{25.91,64.39},{25.82,64.29},{25.76,64.21},{25.70,64.13},{25.65,64.06},
1094
+ {25.59,63.97},{25.55,63.90},{25.52,63.83},{25.48,63.77},{25.45,63.69},{25.43,63.63},{25.41,63.55},{25.40,63.50},
1095
+ {25.39,63.43},{25.40,63.33},{25.40,63.27},{25.42,63.22},{25.44,63.15},{25.46,63.11},{25.50,63.05},{25.53,63.00},
1096
+ {25.58,62.95},{25.63,62.90},{25.69,62.85},{25.75,62.81},{25.77,62.80},{25.34,63.25},{24.84,63.79},{24.37,64.32},
1097
+ {23.92,64.83},{23.48,65.35},{23.08,65.85},{22.65,66.38},{22.28,66.86},{21.98,67.27},{21.67,67.70},{21.36,68.14},
1098
+ {21.05,68.60},{20.74,69.08},{20.50,69.45},{20.27,69.83},{20.04,70.22},{19.82,70.62},{19.60,71.03},{19.38,71.44},
1099
+ {19.17,71.87},{19.03,72.16},{18.83,72.59},{18.71,72.89},{18.52,73.34},{18.40,73.64},{18.28,73.95},{18.17,74.26},
1100
+ {18.01,74.74},{17.91,75.05},{17.82,75.38},{17.72,75.70},{17.64,76.03},{17.56,76.36},{17.48,76.69},{17.41,77.03},
1101
+ {17.35,77.36},{17.29,77.71},{17.24,78.05},{17.19,78.39},{17.15,78.74},{17.12,79.09},{17.09,79.45},{17.07,79.80},
1102
+ {17.05,80.16},{17.04,80.52},{17.04,81.06},{17.04,81.42},{17.05,81.79},{17.07,82.16},{17.08,82.53},{17.11,82.72},
1103
+ {17.14,83.09},{17.18,83.46},{17.22,83.84},{17.27,84.22},{17.33,84.60},{17.39,84.98},{17.48,85.56},{17.56,85.94},
1104
+ {17.64,86.33},{17.73,86.72},{17.81,87.10},{17.91,87.50},{18.04,88.09},{18.16,88.48},{18.27,88.88},{18.40,89.48},
1105
+ {18.57,89.87},{18.69,90.27},{18.88,90.87},{19.03,91.48},{19.22,91.88},{19.44,92.49},{19.66,93.10},{19.85,93.71},
1106
+ {20.04,94.33},{20.33,94.94},{20.60,95.56},{20.85,96.18},{21.10,96.80},{21.19,96.48},{21.27,96.24},{21.38,95.93},
1107
+ {21.47,95.70},{21.59,95.40},{21.72,95.10},{21.86,94.80},{21.97,94.58},{22.12,94.30},{22.27,94.02},{22.43,93.74},
1108
+ {22.64,93.40},{22.81,93.14},{23.04,92.81},{23.22,92.56},{23.45,92.25},{23.68,91.95},{23.92,91.65},{24.21,91.31},
1109
+ {24.45,91.04},{24.74,90.72},{25.08,90.36},{25.37,90.07},{25.70,89.74},{26.08,89.39},{26.44,89.07},{26.87,88.69},
1110
+ {27.27,88.34},{27.72,87.98},{28.19,87.61},{28.68,87.23},{29.21,86.84},{29.48,86.64},{28.99,86.70},{28.13,86.81},
1111
+ {27.28,86.92},{26.56,87.02},{25.83,87.12},{25.18,87.22},{24.57,87.32},{24.01,87.41},{23.53,87.49},{23.03,87.58},
1112
+ {22.53,87.68},{22.10,87.76},{21.68,87.84},{21.26,87.93},{20.92,88.01},{20.58,88.08},{20.25,88.16},{19.92,88.24},
1113
+ {19.59,88.33},{19.35,88.39},{19.12,88.46},{18.81,88.55},{18.58,88.61},{18.36,88.68},{18.14,88.76},{17.93,88.83},
1114
+ {17.79,88.88},{17.59,88.95},{17.39,89.03},{17.26,89.08},{17.08,89.16},{16.96,89.21},{16.79,89.29},{16.68,89.35},
1115
+ {16.58,89.41},{16.43,89.49},{16.33,89.55},{16.24,89.60},{16.16,89.66},{16.04,89.75},{15.96,89.81},{15.89,89.87},
1116
+ {15.83,89.93},{15.77,89.99},{15.71,90.05},{15.66,90.12},{15.61,90.18},{15.57,90.24},{15.54,90.31},{15.51,90.37},
1117
+ {15.48,90.44},{15.46,90.51},{15.40,90.30},{15.30,89.83},{15.21,89.36},{15.12,88.89},{15.03,88.67},{14.99,88.18},
1118
+ {14.92,87.71},{14.85,87.24},{14.78,86.77},{14.75,86.53},{14.70,86.06},{14.65,85.59},{14.61,85.12},{14.60,84.89},
1119
+ {14.57,84.42},{14.54,83.94},{14.53,83.71},{14.52,83.24},{14.51,82.77},{14.52,82.30},{14.52,81.83},{14.53,81.60},
1120
+ {14.55,81.13},{14.58,80.66},{14.59,80.43},{14.63,79.96},{14.68,79.49},{14.70,79.26},{14.76,78.79},{14.82,78.32},
1121
+ {14.85,78.09},{14.93,77.62},{15.01,77.16},{15.10,76.69},{15.19,76.23},{15.24,76.00},{15.34,75.54},{15.45,75.07},
1122
+ {15.57,74.61},{15.69,74.15},{15.82,73.69},{15.96,73.23},{16.10,72.77},{16.24,72.31},{16.39,71.86},{16.55,71.40},
1123
+ {16.71,70.95},{16.96,70.26},{17.14,69.81},{17.32,69.36},{17.59,68.69},{17.88,68.02},{18.07,67.57},{18.37,66.90},
1124
+ {18.67,66.24},{18.99,65.58},{19.30,64.93},{19.74,64.06},{20.07,63.42},{20.51,62.57},{20.97,61.73},{21.54,60.69},
1125
+ {22.00,59.87},{22.70,58.66},{23.39,57.49},{24.19,56.16},{25.20,54.52},{26.38,52.66},{28.55,49.32},{31.32,45.20},
1126
+ {31.15,45.42},{30.99,45.64},{30.85,45.85},{30.72,46.06},{30.57,46.31},{30.47,46.50},{30.34,46.75},{30.23,46.97},
1127
+ {30.13,47.20},{30.03,47.45},{29.93,47.71},{29.86,47.91},{29.77,48.20},{29.71,48.43},{29.65,48.66},{29.58,48.98},
1128
+ {29.53,49.23},{29.48,49.48},{29.44,49.74},{29.41,50.01},{29.37,50.29},{29.35,50.57},{29.33,50.86},{29.31,51.16},
1129
+ {29.30,51.56},{29.29,51.87},{29.29,52.39},{29.30,52.72},{29.31,53.05},{29.33,53.38},{29.35,53.72},{29.37,54.06},
1130
+ {29.40,54.41},{29.43,54.76},{29.47,55.12},{29.52,55.60},{29.56,55.97},{29.61,56.34},{29.66,56.72},{29.73,57.22},
1131
+ {29.79,57.61},{29.84,57.99},{29.93,58.52},{29.99,58.91},{30.08,59.44},{30.15,59.84},{30.24,60.38},{30.34,60.93},
1132
+ {30.42,61.34},{30.52,61.90},{30.63,62.45},{30.73,63.02},{30.85,63.58},{30.96,64.15},{31.08,64.72},{31.19,65.30},
1133
+ {31.31,65.88},{31.44,66.46},{31.59,67.20},{31.72,67.79},{31.88,68.53},{32.01,69.12},{32.18,69.87},{32.25,70.17},
1134
+ {32.06,69.99},{31.76,69.70},{31.45,69.42},{31.21,69.20},{30.97,68.98},{30.68,68.71},{30.44,68.50},{30.21,68.29},
1135
+ {29.98,68.09},{29.75,67.89},{29.53,67.69},{29.31,67.50},{29.09,67.31},{28.88,67.12},{28.72,66.98},{28.52,66.80},
1136
+ {28.31,66.63},{28.16,66.50},{27.97,66.33},{27.78,66.17},{27.64,66.05},{27.49,65.94},{27.33,65.77},{27.20,65.66},
1137
+ {27.04,65.51},{26.92,65.40},{26.81,65.30},{26.66,65.16},{26.55,65.06},{26.45,64.96},{26.35,64.87},
1138
+ }
1139
+
1140
+ -- Matrices used to simulate color vision deficiency (CVD; color blindness).
1141
+ -- Each first-level entry describes CVD type; second-level - severity times 10.
1142
+ -- Source:
1143
+ -- https://www.inf.ufrgs.br/~oliveira/pubs_files/CVD_Simulation/CVD_Simulation.html
1144
+ H.cvd_matrices = {
1145
+ protan = {
1146
+ [00]={{1.000000, 0.000000, -0.000000}, {0.000000, 1.000000, 0.000000}, {-0.000000, -0.000000, 1.000000}},
1147
+ [01]={{0.856167, 0.182038, -0.038205}, {0.029342, 0.955115, 0.015544}, {-0.002880, -0.001563, 1.004443}},
1148
+ [02]={{0.734766, 0.334872, -0.069637}, {0.051840, 0.919198, 0.028963}, {-0.004928, -0.004209, 1.009137}},
1149
+ [03]={{0.630323, 0.465641, -0.095964}, {0.069181, 0.890046, 0.040773}, {-0.006308, -0.007724, 1.014032}},
1150
+ [04]={{0.539009, 0.579343, -0.118352}, {0.082546, 0.866121, 0.051332}, {-0.007136, -0.011959, 1.019095}},
1151
+ [05]={{0.458064, 0.679578, -0.137642}, {0.092785, 0.846313, 0.060902}, {-0.007494, -0.016807, 1.024301}},
1152
+ [06]={{0.385450, 0.769005, -0.154455}, {0.100526, 0.829802, 0.069673}, {-0.007442, -0.022190, 1.029632}},
1153
+ [07]={{0.319627, 0.849633, -0.169261}, {0.106241, 0.815969, 0.077790}, {-0.007025, -0.028051, 1.035076}},
1154
+ [08]={{0.259411, 0.923008, -0.182420}, {0.110296, 0.804340, 0.085364}, {-0.006276, -0.034346, 1.040622}},
1155
+ [09]={{0.203876, 0.990338, -0.194214}, {0.112975, 0.794542, 0.092483}, {-0.005222, -0.041043, 1.046265}},
1156
+ [10]={{0.152286, 1.052583, -0.204868}, {0.114503, 0.786281, 0.099216}, {-0.003882, -0.048116, 1.051998}},
1157
+ },
1158
+ deutan = {
1159
+ [00]={{1.000000, 0.000000, -0.000000}, {0.000000, 1.000000, 0.000000}, {-0.000000, -0.000000, 1.000000}},
1160
+ [01]={{0.866435, 0.177704, -0.044139}, {0.049567, 0.939063, 0.011370}, {-0.003453, 0.007233, 0.996220}},
1161
+ [02]={{0.760729, 0.319078, -0.079807}, {0.090568, 0.889315, 0.020117}, {-0.006027, 0.013325, 0.992702}},
1162
+ [03]={{0.675425, 0.433850, -0.109275}, {0.125303, 0.847755, 0.026942}, {-0.007950, 0.018572, 0.989378}},
1163
+ [04]={{0.605511, 0.528560, -0.134071}, {0.155318, 0.812366, 0.032316}, {-0.009376, 0.023176, 0.986200}},
1164
+ [05]={{0.547494, 0.607765, -0.155259}, {0.181692, 0.781742, 0.036566}, {-0.010410, 0.027275, 0.983136}},
1165
+ [06]={{0.498864, 0.674741, -0.173604}, {0.205199, 0.754872, 0.039929}, {-0.011131, 0.030969, 0.980162}},
1166
+ [07]={{0.457771, 0.731899, -0.189670}, {0.226409, 0.731012, 0.042579}, {-0.011595, 0.034333, 0.977261}},
1167
+ [08]={{0.422823, 0.781057, -0.203881}, {0.245752, 0.709602, 0.044646}, {-0.011843, 0.037423, 0.974421}},
1168
+ [09]={{0.392952, 0.823610, -0.216562}, {0.263559, 0.690210, 0.046232}, {-0.011910, 0.040281, 0.971630}},
1169
+ [10]={{0.367322, 0.860646, -0.227968}, {0.280085, 0.672501, 0.047413}, {-0.011820, 0.042940, 0.968881}},
1170
+ },
1171
+ tritan = {
1172
+ [00]={{1.000000, 0.000000, -0.000000}, {0.000000, 1.000000, 0.000000}, {-0.000000, -0.000000, 1.000000}},
1173
+ [01]={{0.926670, 0.092514, -0.019184}, {0.021191, 0.964503, 0.014306}, {0.008437, 0.054813, 0.936750}},
1174
+ [02]={{0.895720, 0.133330, -0.029050}, {0.029997, 0.945400, 0.024603}, {0.013027, 0.104707, 0.882266}},
1175
+ [03]={{0.905871, 0.127791, -0.033662}, {0.026856, 0.941251, 0.031893}, {0.013410, 0.148296, 0.838294}},
1176
+ [04]={{0.948035, 0.089490, -0.037526}, {0.014364, 0.946792, 0.038844}, {0.010853, 0.193991, 0.795156}},
1177
+ [05]={{1.017277, 0.027029, -0.044306}, {-0.006113, 0.958479, 0.047634}, {0.006379, 0.248708, 0.744913}},
1178
+ [06]={{1.104996, -0.046633, -0.058363}, {-0.032137, 0.971635, 0.060503}, {0.001336, 0.317922, 0.680742}},
1179
+ [07]={{1.193214, -0.109812, -0.083402}, {-0.058496, 0.979410, 0.079086}, {-0.002346, 0.403492, 0.598854}},
1180
+ [08]={{1.257728, -0.139648, -0.118081}, {-0.078003, 0.975409, 0.102594}, {-0.003316, 0.501214, 0.502102}},
1181
+ [09]={{1.278864, -0.125333, -0.153531}, {-0.084748, 0.957674, 0.127074}, {-0.000989, 0.601151, 0.399838}},
1182
+ [10]={{1.255528, -0.076749, -0.178779}, {-0.078411, 0.930809, 0.147602}, {0.004733, 0.691367, 0.303900}},
1183
+ },
1184
+ }
1185
+ ---@diagnostic disable end
1186
+ --stylua: ignore end
1187
+
1188
+ H.allowed_spaces = { '8-bit', 'hex', 'rgb', 'oklab', 'oklch', 'okhsl' }
1189
+
1190
+ H.allowed_channels =
1191
+ { 'lightness', 'chroma', 'saturation', 'hue', 'temperature', 'pressure', 'a', 'b', 'red', 'green', 'blue' }
1192
+
1193
+ H.ns_id = { interactive = vim.api.nvim_create_namespace('MiniColorsInteractive') }
1194
+
1195
+ -- Helper functionality =======================================================
1196
+ -- Settings -------------------------------------------------------------------
1197
+ H.setup_config = function(config)
1198
+ H.check_type('config', config, 'table', true)
1199
+ config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
1200
+
1201
+ return config
1202
+ end
1203
+
1204
+ H.apply_config = function(config) MiniColors.config = config end
1205
+
1206
+ H.get_config = function(config)
1207
+ return vim.tbl_deep_extend('force', MiniColors.config, vim.b.minicolors_config or {}, config or {})
1208
+ end
1209
+
1210
+ H.create_user_commands = function()
1211
+ local callback = function(input)
1212
+ local cs_array = vim.tbl_map(MiniColors.get_colorscheme, input.fargs)
1213
+ MiniColors.animate(cs_array)
1214
+ end
1215
+ vim.api.nvim_create_user_command('Colorscheme', callback, { nargs = '+', complete = 'color' })
1216
+ end
1217
+
1218
+ -- Color scheme methods -------------------------------------------------------
1219
+ H.cs_add_cterm_attributes = function(self, opts)
1220
+ local res = vim.deepcopy(self)
1221
+ opts = vim.tbl_deep_extend('force', { force = true }, opts or {})
1222
+
1223
+ -- Compute Oklab coordinates of terminal colors for better approximation
1224
+ local term_oklab = H.compute_term_oklab()
1225
+
1226
+ local force = opts.force
1227
+ for _, gr in pairs(res.groups) do
1228
+ if gr.fg and (force or not gr.ctermfg) then gr.ctermfg = H.get_closest_color_id(gr.fg, term_oklab) end
1229
+ if gr.bg and (force or not gr.ctermbg) then gr.ctermbg = H.get_closest_color_id(gr.bg, term_oklab) end
1230
+ end
1231
+
1232
+ return res
1233
+ end
1234
+
1235
+ H.cs_add_terminal_colors = function(self, opts)
1236
+ local res = vim.deepcopy(self)
1237
+ opts = vim.tbl_deep_extend('force', { force = true, palette_args = {} }, opts or {})
1238
+
1239
+ -- General meaning of terminal colors are taken from here:
1240
+ -- https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
1241
+ -- Regular and bright versions will be equal (to simplify algorithm)
1242
+
1243
+ -- Compress (for better palette representation) and resolve links (accounts
1244
+ -- for possibly linked 'Normal' group)
1245
+ local cs = res:compress():resolve_links()
1246
+
1247
+ -- Get palette and convert in Oklch
1248
+ local palette = cs:get_palette(opts.palette_args)
1249
+ local palette_oklch = vim.tbl_map(function(x) return MiniColors.convert(x, 'oklch') end, palette)
1250
+
1251
+ local terminal = {}
1252
+
1253
+ -- Black and white are colors from `Normal` group.
1254
+ local normal = cs.groups.Normal or {}
1255
+ local black, white = normal.bg, normal.fg
1256
+
1257
+ terminal[0], terminal[8] = black, black
1258
+ terminal[7], terminal[15] = white, white
1259
+
1260
+ -- Colors are computed as closest to reference (pre-defined hue with white
1261
+ -- lightness) taking into account only normalized lightness and hue
1262
+ local white_oklch = MiniColors.convert(white, 'oklch')
1263
+ local ref_l = white ~= nil and white_oklch.l or (vim.o.background == 'dark' and 85 or 15)
1264
+ local ref_color_data = {
1265
+ { l = ref_l, h = 30 }, -- Red
1266
+ { l = ref_l, h = 150 }, -- Green
1267
+ { l = ref_l, h = 90 }, -- Yellow
1268
+ { l = ref_l, h = 270 }, -- Blue
1269
+ { l = ref_l, h = 330 }, -- Magenta
1270
+ { l = ref_l, h = 210 }, -- Cyan
1271
+ }
1272
+ local dist_color = function(x, y) return H.dist(x.l, y.l) / 100 + H.dist_circle(x.h, y.h) / 90 end
1273
+ for i, ref in ipairs(ref_color_data) do
1274
+ local col = H.get_closest(ref, palette_oklch, dist_color)
1275
+ terminal[i], terminal[i + 8] = col, col
1276
+ end
1277
+
1278
+ -- Update current `terminal` field
1279
+ for i = 0, 15 do
1280
+ if opts.force or not res.terminal[i] then res.terminal[i] = MiniColors.convert(terminal[i], 'hex') end
1281
+ end
1282
+
1283
+ return res
1284
+ end
1285
+
1286
+ H.cs_add_transparency = function(self, opts)
1287
+ opts = vim.tbl_deep_extend('force', {
1288
+ general = true,
1289
+ float = false,
1290
+ statuscolumn = false,
1291
+ statusline = false,
1292
+ tabline = false,
1293
+ winbar = false,
1294
+ }, opts or {})
1295
+
1296
+ local res = vim.deepcopy(self)
1297
+ local groups = res.groups
1298
+ local update = function(names)
1299
+ for _, n in pairs(names) do
1300
+ local gr = groups[n]
1301
+ if gr == nil then return end
1302
+ gr.bg, gr.ctermbg = nil, nil
1303
+ gr.blend = 0
1304
+ end
1305
+ end
1306
+
1307
+ if opts.general then
1308
+ update({ 'Normal', 'NormalNC', 'EndOfBuffer', 'MsgArea', 'MsgSeparator', 'VertSplit', 'WinSeparator' })
1309
+ update({ 'ErrorMsg', 'WarningMsg', 'OkMsg', 'ModeMsg', 'MoreMsg', 'StderrMsg', 'StdoutMsg' })
1310
+ end
1311
+
1312
+ if opts.float then update({ 'FloatBorder', 'FloatTitle', 'NormalFloat' }) end
1313
+
1314
+ if opts.statuscolumn then
1315
+ update({ 'FoldColumn', 'LineNr', 'LineNrAbove', 'LineNrBelow', 'SignColumn' })
1316
+ local diag_hl = vim.tbl_map(function(x) return 'DiagnosticSign' .. x end, { 'Error', 'Warn', 'Info', 'Hint', 'Ok' })
1317
+ update(diag_hl)
1318
+
1319
+ -- Remove statuscolumn background coming from signs
1320
+ local signs = vim.fn.sign_getdefined()
1321
+ local groups = {}
1322
+ for _, sign in ipairs(vim.fn.sign_getdefined()) do
1323
+ table.insert(groups, sign.texthl)
1324
+ table.insert(groups, sign.numhl)
1325
+ end
1326
+ update(groups)
1327
+ end
1328
+
1329
+ if opts.statusline then update({ 'StatusLine', 'StatusLineNC', 'StatusLineTerm', 'StatusLineTermNC' }) end
1330
+
1331
+ if opts.tabline then update({ 'TabLine', 'TabLineFill', 'TabLineSel' }) end
1332
+
1333
+ if opts.winbar then update({ 'WinBar', 'WinBarNC' }) end
1334
+
1335
+ return res
1336
+ end
1337
+
1338
+ H.cs_apply = function(self, opts)
1339
+ opts = vim.tbl_deep_extend('force', { clear = true }, opts or {})
1340
+
1341
+ if opts.clear then vim.cmd('highlight clear') end
1342
+ vim.g.colors_name = self.name
1343
+
1344
+ -- Highlight groups
1345
+ local hi = vim.api.nvim_set_hl
1346
+ local groups_arr = H.hl_groups_to_array(self.groups)
1347
+ for _, hl_data in ipairs(groups_arr) do
1348
+ hi(0, hl_data.name, hl_data.spec)
1349
+ end
1350
+
1351
+ -- Terminal colors. Apply all colors in order to possibly remove previously
1352
+ -- set ones.
1353
+ for i = 0, 15 do
1354
+ vim.g['terminal_color_' .. i] = self.terminal[i]
1355
+ end
1356
+
1357
+ return self
1358
+ end
1359
+
1360
+ H.cs_chan_add = function(self, channel, value, opts)
1361
+ channel = H.normalize_channel(channel)
1362
+ value = H.normalize_number(value, 'value')
1363
+ if value == 0 then return vim.deepcopy(self) end
1364
+
1365
+ return self:chan_modify(channel, function(x) return x + value end, opts)
1366
+ end
1367
+
1368
+ H.cs_chan_invert = function(self, channel, opts)
1369
+ channel = H.normalize_channel(channel)
1370
+ -- Don't invert 'chroma' directly because of lack of useful reference point
1371
+ if channel == 'chroma' then channel = 'saturation' end
1372
+ return self:chan_modify(channel, H.chan_inverters[channel], opts)
1373
+ end
1374
+
1375
+ H.cs_chan_modify = function(self, channel, f, opts)
1376
+ channel = H.normalize_channel(channel)
1377
+ f = H.normalize_f(f)
1378
+ opts = opts or {}
1379
+ local filter = H.normalize_filter(opts.filter)
1380
+ local gamut_clip = H.normalize_gamut_clip(opts.gamut_clip)
1381
+
1382
+ local modify_channel = H.channel_modifiers[channel]
1383
+
1384
+ local f_color = function(hex, data)
1385
+ if not filter(hex, data) then return hex end
1386
+ return modify_channel(hex, f, gamut_clip)
1387
+ end
1388
+
1389
+ return self:color_modify(f_color)
1390
+ end
1391
+
1392
+ H.cs_chan_multiply = function(self, channel, coef, opts)
1393
+ channel = H.normalize_channel(channel)
1394
+ coef = H.normalize_number(coef, 'coef')
1395
+ if coef == 1 then return vim.deepcopy(self) end
1396
+
1397
+ return self:chan_modify(channel, function(x) return coef * x end, opts)
1398
+ end
1399
+
1400
+ H.cs_chan_repel = function(self, channel, sources, coef, opts)
1401
+ channel = H.normalize_channel(channel)
1402
+ sources = H.normalize_number_array(sources, 'sources')
1403
+ coef = H.normalize_number(coef, 'coef')
1404
+
1405
+ if #sources == {} or coef == 0 then return vim.deepcopy(self) end
1406
+
1407
+ -- Account for periodic nature of "hue" channel
1408
+ sources = channel == 'hue' and H.add_circle_sources(sources) or sources
1409
+ local tie_breaker = H.repel_tie_breakers[channel]
1410
+ local f = function(x) return H.repel(x, sources, coef, tie_breaker) end
1411
+
1412
+ return self:chan_modify(channel, f, opts)
1413
+ end
1414
+
1415
+ H.cs_chan_set = function(self, channel, values, opts)
1416
+ channel = H.normalize_channel(channel)
1417
+ values = H.normalize_number_array(values, 'values')
1418
+ if #values == 0 then return H.error('Argument `values` should not be empty.') end
1419
+
1420
+ local dist_fun = channel == 'hue' and H.dist_circle or H.dist
1421
+ local f = function(x) return H.get_closest(x, values, dist_fun) end
1422
+
1423
+ return self:chan_modify(channel, f, opts)
1424
+ end
1425
+
1426
+ H.cs_color_modify = function(self, f)
1427
+ f = H.normalize_f(f)
1428
+
1429
+ local res = vim.deepcopy(self)
1430
+
1431
+ -- Highlight groups
1432
+ for name, spec in pairs(res.groups) do
1433
+ if spec.fg ~= nil then spec.fg = f(spec.fg, { attr = 'fg', name = name }) end
1434
+ if spec.bg ~= nil then spec.bg = f(spec.bg, { attr = 'bg', name = name }) end
1435
+ if spec.sp ~= nil then spec.sp = f(spec.sp, { attr = 'sp', name = name }) end
1436
+ end
1437
+
1438
+ -- Terminal colors
1439
+ for i, hex in pairs(res.terminal) do
1440
+ res.terminal[i] = f(hex, { attr = 'term', name = 'terminal_color_' .. i })
1441
+ end
1442
+
1443
+ return res
1444
+ end
1445
+
1446
+ H.cs_compress = function(self, opts)
1447
+ opts = vim.tbl_deep_extend('force', { plugins = true }, opts or {})
1448
+ local current_cs = MiniColors.get_colorscheme()
1449
+
1450
+ vim.cmd('highlight clear')
1451
+ local clear_cs_groups = MiniColors.get_colorscheme().groups
1452
+
1453
+ local new_groups = {}
1454
+ for name, spec in pairs(self.groups) do
1455
+ -- Group should stay only if it adds new information compared to the state
1456
+ -- after `:hi clear`
1457
+ local is_from_clear = vim.deep_equal(clear_cs_groups[name], spec)
1458
+
1459
+ -- `^DevIcon` groups come from 'nvim-tree/nvim-web-devicons' and don't
1460
+ -- really have value outside of that plugin. Plus there are **many** of
1461
+ -- them and they are created in that plugin.
1462
+ local is_devicon = opts.plugins and name:find('^DevIcon') ~= nil
1463
+
1464
+ -- `^colorizer_` groups come from 'norcalli/nvim-colorizer.lua' plugin and
1465
+ -- don't really have value outside of that plugin.
1466
+ local is_colorizer = opts.plugins and name:find('^colorizer_') ~= nil
1467
+
1468
+ -- 'mini.hipatterns' defines groups for hex color highlighting
1469
+ local is_hipatterns_hex_color = opts.plugins and name:find('^MiniHipatterns%x%x%x%x%x%x$') ~= nil
1470
+
1471
+ if not (is_from_clear or is_devicon or is_colorizer) then new_groups[name] = spec end
1472
+ end
1473
+
1474
+ current_cs:apply()
1475
+
1476
+ return MiniColors.as_colorscheme({ name = self.name, groups = new_groups, terminal = self.terminal })
1477
+ end
1478
+
1479
+ H.cs_get_palette = function(self, opts)
1480
+ opts = vim.tbl_deep_extend('force', { threshold = 0.01 }, opts or {})
1481
+
1482
+ -- Traverse all colors
1483
+ local colors, n_color_uses = {}, 0
1484
+ self:color_modify(function(hex)
1485
+ colors[hex] = (colors[hex] or 0) + 1
1486
+ n_color_uses = n_color_uses + 1
1487
+ end)
1488
+
1489
+ -- Filter out and sort in descending order of lightness
1490
+ local all_colors = {}
1491
+ for hex, count in pairs(colors) do
1492
+ if opts.threshold <= (count / n_color_uses) then
1493
+ table.insert(all_colors, { hex, MiniColors.convert(hex, 'oklch').l })
1494
+ end
1495
+ end
1496
+ table.sort(all_colors, function(a, b) return a[2] < b[2] end)
1497
+
1498
+ return vim.tbl_map(function(x) return x[1] end, all_colors)
1499
+ end
1500
+
1501
+ H.cs_resolve_links = function(self)
1502
+ local res = vim.deepcopy(self)
1503
+
1504
+ -- Resolve direct links (highlight groups linking to group without link)
1505
+ -- iteratively one level at a time
1506
+ repeat
1507
+ local n_resolved_links = 0
1508
+ for hl_name, hl_data in pairs(res.groups) do
1509
+ -- Resolve link only if:
1510
+ -- - Current highlight group is linked.
1511
+ -- - Target link is present in color scheme and is not itself linked.
1512
+ local link_data = res.groups[hl_data.link]
1513
+ if link_data ~= nil and link_data.link == nil then
1514
+ res.groups[hl_name] = vim.deepcopy(res.groups[hl_data.link])
1515
+ n_resolved_links = n_resolved_links + 1
1516
+ end
1517
+ end
1518
+ until n_resolved_links == 0
1519
+
1520
+ return res
1521
+ end
1522
+
1523
+ H.cs_simulate_cvd = function(self, cvd_type, severity, opts)
1524
+ local f = function(hex) return MiniColors.simulate_cvd(hex, cvd_type, severity, opts) end
1525
+ return self:color_modify(f)
1526
+ end
1527
+
1528
+ H.cs_write = function(self, opts)
1529
+ opts = vim.tbl_extend(
1530
+ 'force',
1531
+ { compress = true, directory = (vim.fn.stdpath('config') .. '/colors'), name = nil },
1532
+ opts or {}
1533
+ )
1534
+
1535
+ local name = opts.name or H.make_file_basename(self.name or 'mini_colors')
1536
+
1537
+ local cs = opts.compress and self:compress() or self
1538
+
1539
+ -- Create file lines
1540
+ -- - Header
1541
+ local lines = {
1542
+ [[-- Made with 'mini.colors' module of https://nvim-mini.org/mini.nvim]],
1543
+ '',
1544
+ [[if vim.g.colors_name ~= nil then vim.cmd('highlight clear') end]],
1545
+ 'vim.g.colors_name = ' .. vim.inspect(cs.name),
1546
+ }
1547
+
1548
+ -- - Highlight groups
1549
+ if vim.tbl_count(cs.groups) > 0 then
1550
+ vim.list_extend(lines, { '', '-- Highlight groups', 'local hi = vim.api.nvim_set_hl', '' })
1551
+ else
1552
+ vim.list_extend(lines, { '', '-- No highlight groups defined' })
1553
+ end
1554
+
1555
+ local make_hi_line = function(hl)
1556
+ local spec = setmetatable(hl.spec, nil)
1557
+ return string.format('hi(0, "%s", %s)', hl.name, vim.inspect(spec, { newline = ' ', indent = '' }))
1558
+ end
1559
+ vim.list_extend(lines, vim.tbl_map(make_hi_line, H.hl_groups_to_array(cs.groups)))
1560
+
1561
+ -- - Terminal colors
1562
+ if vim.tbl_count(cs.terminal) > 0 then
1563
+ vim.list_extend(lines, { '', '-- Terminal colors', 'local g = vim.g', '' })
1564
+ else
1565
+ vim.list_extend(lines, { '', '-- No terminal colors defined' })
1566
+ end
1567
+
1568
+ for i, hex in pairs(cs.terminal) do
1569
+ local l = string.format('g.terminal_color_%d = "%s"', i, hex)
1570
+ table.insert(lines, l)
1571
+ end
1572
+
1573
+ -- Create file and populate with computed lines
1574
+ vim.fn.mkdir(opts.directory, 'p')
1575
+ local path = string.format('%s/%s.lua', opts.directory, name)
1576
+ vim.fn.writefile(lines, path)
1577
+
1578
+ return self
1579
+ end
1580
+
1581
+ H.is_colorscheme = function(x) return type(x) == 'table' and type(x.groups) == 'table' and type(x.terminal) == 'table' end
1582
+
1583
+ H.normalize_f = function(f)
1584
+ if not vim.is_callable(f) then H.error('Argument `f` should be callable.') end
1585
+ return f
1586
+ end
1587
+
1588
+ H.normalize_channel = function(x)
1589
+ if not vim.tbl_contains(H.allowed_channels, x) then
1590
+ local allowed = table.concat(vim.tbl_map(vim.inspect, H.allowed_channels), ', ')
1591
+ local msg = string.format('Channel should be one of %s. Not %s.', allowed, vim.inspect(x))
1592
+ H.error(msg)
1593
+ end
1594
+ return x
1595
+ end
1596
+
1597
+ H.normalize_filter = function(x)
1598
+ -- Treat `nil` filter as no filter
1599
+ if x == nil then x = function() return true end end
1600
+
1601
+ -- Treat string filter as filter on attribute ('fg', 'bg', etc.)
1602
+ if x == 'fg' or x == 'bg' or x == 'sp' or x == 'term' then
1603
+ local attr_val = x
1604
+ x = function(_, data) return data.attr == attr_val end
1605
+ end
1606
+
1607
+ if not vim.is_callable(x) then
1608
+ H.error('Argument `opts.filter` should be either proper attribute string or callable.')
1609
+ end
1610
+
1611
+ return x
1612
+ end
1613
+
1614
+ H.normalize_gamut_clip = function(x)
1615
+ x = x or 'chroma'
1616
+ if x == 'chroma' or x == 'lightness' or x == 'cusp' then return x end
1617
+ H.error('Argument `opts.gamut_clip` should one of "chroma", "lightness", "cusp".')
1618
+ end
1619
+
1620
+ H.normalize_number = function(x, arg_name)
1621
+ if type(x) ~= 'number' then H.error('Argument `' .. arg_name .. '` should be a number.') end
1622
+ return x
1623
+ end
1624
+
1625
+ H.normalize_number_array = function(x, arg_name)
1626
+ if H.is_number(x) then x = { x } end
1627
+ if not (H.is_table(x) and H.all(x, H.is_number)) then
1628
+ H.error('Argument `' .. arg_name .. '` should be number or array of numbers.')
1629
+ end
1630
+ return x
1631
+ end
1632
+
1633
+ -- Color scheme helpers -------------------------------------------------------
1634
+ H.make_file_basename = function(name)
1635
+ -- If there already is color scheme named `name`, append unique suffix
1636
+ local all_colorschemes = vim.fn.getcompletion('', 'color')
1637
+
1638
+ if not vim.tbl_contains(all_colorschemes, name) then return name end
1639
+ return name .. vim.fn.strftime('_%Y%m%d_%H%M%S')
1640
+ end
1641
+
1642
+ -- -- TODO: Use `vim.api.nvim_get_hl()` when it is more stable (doesn't have
1643
+ -- issues with including not created highlight highlight groups for semantic
1644
+ -- tokens)
1645
+ -- H.get_current_groups = function()
1646
+ -- local res = {}
1647
+ --
1648
+ -- for name, new_t in pairs(vim.api.nvim_get_hl(0, {})) do
1649
+ -- -- Return plain `{}` instead of `vim.empty_dict()`
1650
+ -- local new_t = setmetatable(new_t, nil)
1651
+ -- -- Use HEX when needed
1652
+ -- new_t.fg, new_t.bg, new_t.sp = H.dec2hex(new_t.fg), H.dec2hex(new_t.bg), H.dec2hex(new_t.sp)
1653
+ -- res[name] = new_t
1654
+ -- end
1655
+ --
1656
+ -- return res
1657
+ -- end
1658
+
1659
+ H.get_current_groups = function()
1660
+ -- Get present highlight group names and if they are linked
1661
+ local group_data = vim.split(vim.api.nvim_exec('highlight', true), '\n')
1662
+ local group_names = vim.tbl_map(function(x) return x:match('^(%S+)') end, group_data)
1663
+ local link_data = vim.tbl_map(function(x) return x:match('^%S+.* links to (%S+)$') end, group_data)
1664
+
1665
+ local res = {}
1666
+ for i, name in pairs(group_names) do
1667
+ if link_data[i] ~= nil then
1668
+ res[name] = { link = link_data[i] }
1669
+ else
1670
+ res[name] = H.get_hl_by_name(name)
1671
+ end
1672
+ end
1673
+ return res
1674
+ end
1675
+
1676
+ H.get_hl_by_name = function(name)
1677
+ local res = vim.api.nvim_get_hl_by_name(name, true)
1678
+
1679
+ -- Convert decimal colors to hex strings
1680
+ res.fg = H.dec2hex(res.foreground)
1681
+ res.bg = H.dec2hex(res.background)
1682
+ res.sp = H.dec2hex(res.special)
1683
+
1684
+ res.foreground, res.background, res.special = nil, nil, nil
1685
+
1686
+ -- Add terminal colors
1687
+ local cterm_data = vim.api.nvim_get_hl_by_name(name, false)
1688
+ res.ctermfg = cterm_data.foreground
1689
+ res.ctermbg = cterm_data.background
1690
+
1691
+ -- At the moment, having `res[true] = 6` indicates that group is cleared
1692
+ -- NOTE: actually return empty dictionary and not `nil` to preserve
1693
+ -- information that group was cleared. This might matter if highlight group
1694
+ -- was cleared but default links to something else (like if group
1695
+ -- `@lsp.type.variable` is cleared to use tree-sitter highlighting but by
1696
+ -- default it links to `Identifier`).
1697
+ res[true] = nil
1698
+
1699
+ -- Return plain `{}` instead of `vim.empty_dict()`
1700
+ return setmetatable(res, nil)
1701
+ end
1702
+
1703
+ H.get_current_terminal = function()
1704
+ local res = {}
1705
+ for i = 0, 15 do
1706
+ local col = vim.g['terminal_color_' .. i]
1707
+ if type(col) == 'string' then res[i] = H.dec2hex(vim.api.nvim_get_color_by_name(col)) end
1708
+ end
1709
+
1710
+ return res
1711
+ end
1712
+
1713
+ H.dec2hex = function(dec)
1714
+ if dec == nil or dec < 0 then return nil end
1715
+ return string.format('#%06x', dec)
1716
+ end
1717
+
1718
+ H.hl_groups_to_array = function(hl_groups)
1719
+ local res = {}
1720
+ for name, spec in pairs(hl_groups) do
1721
+ table.insert(res, { name = name, spec = spec })
1722
+ end
1723
+ table.sort(res, function(a, b) return a.name < b.name end)
1724
+ return res
1725
+ end
1726
+
1727
+ -- Terminal colors ------------------------------------------------------------
1728
+ -- Source: https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
1729
+ H.compute_term_oklab = function()
1730
+ -- Use cached values if they are already computed
1731
+ if H.term_oklab ~= nil then return H.term_oklab end
1732
+
1733
+ local res = {}
1734
+
1735
+ -- Main colors. Don't use 0-15 because they are terminal dependent
1736
+ local cterm_basis = { 0, 95, 135, 175, 215, 255 }
1737
+ for i = 16, 231 do
1738
+ local j = i - 16
1739
+ local r = cterm_basis[math.floor(j / 36) % 6 + 1]
1740
+ local g = cterm_basis[math.floor(j / 6) % 6 + 1]
1741
+ local b = cterm_basis[j % 6 + 1]
1742
+ res[i] = MiniColors.convert({ r = r, g = g, b = b }, 'oklab')
1743
+ end
1744
+
1745
+ -- Grays
1746
+ for i = 232, 255 do
1747
+ local c = 8 + (i - 232) * 10
1748
+ res[i] = MiniColors.convert({ r = c, g = c, b = c }, 'oklab')
1749
+ end
1750
+
1751
+ H.term_oklab = res
1752
+ return res
1753
+ end
1754
+
1755
+ H.get_closest_color_id = function(x, ref_oklab)
1756
+ local _, res = H.get_closest(MiniColors.convert(x, 'oklab'), ref_oklab, H.dist_oklab)
1757
+ return res
1758
+ end
1759
+
1760
+ -- Animation ------------------------------------------------------------------
1761
+ H.animate_single_transition = function(from_cs, to_cs, after_action, opts)
1762
+ local all_group_names = H.union(vim.tbl_keys(from_cs.groups), vim.tbl_keys(to_cs.groups))
1763
+ local n_steps = math.max(opts.transition_steps, 1)
1764
+ local step_duration = math.max(opts.transition_duration / n_steps, 1)
1765
+
1766
+ -- Start animation
1767
+ local cur_step = 1
1768
+ local timer = vim.loop.new_timer()
1769
+
1770
+ local apply_step
1771
+ apply_step = vim.schedule_wrap(function()
1772
+ -- Ensure that current step is not too big. This handles weird issue with
1773
+ -- small `step_duration` when this continued calling after `timer:stop()`.
1774
+ -- Probably due to considerable time it takes to execute single step.
1775
+ if n_steps < cur_step then return end
1776
+
1777
+ -- Compute and apply transition step
1778
+ local cs_step = H.compute_animate_step(from_cs, to_cs, cur_step / n_steps, all_group_names)
1779
+ -- - Use implementation helper instead of using `as_colorscheme()` to avoid
1780
+ -- unnecessary `deepcopy()` increasing performance
1781
+ H.cs_apply(cs_step)
1782
+ vim.cmd('redraw')
1783
+
1784
+ -- Advance
1785
+ cur_step = cur_step + 1
1786
+ if n_steps < cur_step then
1787
+ timer:stop()
1788
+ pcall(after_action, { n_steps = n_steps, cur_step = cur_step })
1789
+ return
1790
+ end
1791
+
1792
+ -- Handle timer repeat here in order to ensure concurrency of steps
1793
+ timer:set_repeat(step_duration)
1794
+ timer:again()
1795
+ end)
1796
+
1797
+ -- Start non-repeating timer
1798
+ timer:start(step_duration, 0, apply_step)
1799
+ end
1800
+
1801
+ H.cs_hex_to_oklab = function(cs)
1802
+ local to_oklab = function(hex) return MiniColors.convert(hex, 'oklab') end
1803
+ cs.groups = vim.tbl_map(function(gr)
1804
+ gr.fg, gr.bg, gr.sp = to_oklab(gr.fg), to_oklab(gr.bg), to_oklab(gr.sp)
1805
+ return gr
1806
+ end, cs.groups)
1807
+
1808
+ cs.terminal = vim.tbl_map(to_oklab, cs.terminal)
1809
+
1810
+ return cs
1811
+ end
1812
+
1813
+ H.cs_oklab_to_hex = function(cs)
1814
+ -- 'chroma' clipping preserves lightness resulting into smoother transitions
1815
+ local to_hex = function(lab) return MiniColors.convert(lab, 'hex', { gamut_clip = 'chroma' }) end
1816
+ cs.groups = vim.tbl_map(function(gr)
1817
+ gr.fg, gr.bg, gr.sp = to_hex(gr.fg), to_hex(gr.bg), to_hex(gr.sp)
1818
+ return gr
1819
+ end, cs.groups)
1820
+
1821
+ cs.terminal = vim.tbl_map(to_hex, cs.terminal)
1822
+
1823
+ return cs
1824
+ end
1825
+
1826
+ H.compute_animate_step = function(from, to, coef, all_group_names)
1827
+ local groups = {}
1828
+ for _, name in ipairs(all_group_names) do
1829
+ groups[name] = H.convex_hl_group(from.groups[name], to.groups[name], coef)
1830
+ end
1831
+
1832
+ local terminal = {}
1833
+ for i = 0, 15 do
1834
+ terminal[i] = H.convex_lab(from.terminal[i], to.terminal[i], coef)
1835
+ end
1836
+
1837
+ local cs_data = { name = 'transition_step', groups = groups, terminal = terminal }
1838
+ return H.cs_oklab_to_hex(cs_data)
1839
+ end
1840
+
1841
+ H.convex_hl_group = function(from, to, coef)
1842
+ if from == nil or to == nil or from.link ~= nil or to.link ~= nil then return H.convex_discrete(from, to, coef) end
1843
+
1844
+ --stylua: ignore
1845
+ return {
1846
+ -- No `cterm` in convex combination because it is not trivial to create
1847
+ -- proper gradient for them
1848
+ fg = H.convex_lab(from.fg, to.fg, coef),
1849
+ bg = H.convex_lab(from.bg, to.bg, coef),
1850
+ sp = H.convex_lab(from.sp, to.sp, coef),
1851
+
1852
+ blend = H.round(H.convex_continuous(from.blend, to.blend, coef)),
1853
+
1854
+ bold = H.convex_discrete(from.bold, to.bold, coef),
1855
+ italic = H.convex_discrete(from.italic, to.italic, coef),
1856
+ nocombine = H.convex_discrete(from.nocombine, to.nocombine, coef),
1857
+ reverse = H.convex_discrete(from.reverse, to.reverse, coef),
1858
+ standout = H.convex_discrete(from.standout, to.standout, coef),
1859
+ strikethrough = H.convex_discrete(from.strikethrough, to.strikethrough, coef),
1860
+ undercurl = H.convex_discrete(from.undercurl, to.undercurl, coef),
1861
+ underdashed = H.convex_discrete(from.underdashed, to.underdashed, coef),
1862
+ underdotted = H.convex_discrete(from.underdotted, to.underdotted, coef),
1863
+ underdouble = H.convex_discrete(from.underdouble, to.underdouble, coef),
1864
+ underline = H.convex_discrete(from.underline, to.underline, coef),
1865
+ }
1866
+ end
1867
+
1868
+ H.convex_lab = function(from_lab, to_lab, coef)
1869
+ if from_lab == nil or to_lab == nil then return H.convex_discrete(from_lab, to_lab, coef) end
1870
+ return {
1871
+ l = H.convex_continuous(from_lab.l, to_lab.l, coef),
1872
+ a = H.convex_continuous(from_lab.a, to_lab.a, coef),
1873
+ b = H.convex_continuous(from_lab.b, to_lab.b, coef),
1874
+ }
1875
+ end
1876
+
1877
+ -- Channel modifiers ----------------------------------------------------------
1878
+ H.channel_modifiers = {}
1879
+
1880
+ H.channel_modifiers.lightness = function(hex, f, gamut_clip)
1881
+ local lch = MiniColors.convert(hex, 'oklch')
1882
+ lch.l = H.clip(f(lch.l), 0, 100)
1883
+ return MiniColors.convert(lch, 'hex', { gamut_clip = gamut_clip })
1884
+ end
1885
+
1886
+ H.channel_modifiers.chroma = function(hex, f, gamut_clip)
1887
+ local lch = MiniColors.convert(hex, 'oklch')
1888
+ lch.c = H.clip(f(lch.c), 0, math.huge)
1889
+ return MiniColors.convert(lch, 'hex', { gamut_clip = gamut_clip })
1890
+ end
1891
+
1892
+ H.channel_modifiers.saturation = function(hex, f, gamut_clip)
1893
+ local lsh = MiniColors.convert(hex, 'okhsl')
1894
+ lsh.s = H.clip(f(lsh.s), 0, 100)
1895
+ return MiniColors.convert(lsh, 'hex', { gamut_clip = gamut_clip })
1896
+ end
1897
+
1898
+ H.channel_modifiers.hue = function(hex, f, gamut_clip)
1899
+ local lch = MiniColors.convert(hex, 'oklch')
1900
+ if lch.h == nil then return hex end
1901
+ lch.h = f(lch.h) % 360
1902
+ return MiniColors.convert(lch, 'hex', { gamut_clip = gamut_clip })
1903
+ end
1904
+
1905
+ H.channel_modifiers.temperature = function(hex, f, gamut_clip)
1906
+ local lch = MiniColors.convert(hex, 'oklch')
1907
+ if lch.h == nil then return hex end
1908
+
1909
+ -- Temperature is a circular distance to 270 hue degrees
1910
+ -- Output value will lie in the same vertical half plane
1911
+ local is_left = 90 <= lch.h and lch.h < 270
1912
+ local temp = (is_left and (270 - lch.h) or (lch.h + 90)) % 360
1913
+ local new_temp = H.clip(f(temp), 0, 180)
1914
+ lch.h = (is_left and (270 - new_temp) or (new_temp - 90)) % 360
1915
+
1916
+ return MiniColors.convert(lch, 'hex', { gamut_clip = gamut_clip })
1917
+ end
1918
+
1919
+ H.channel_modifiers.pressure = function(hex, f, gamut_clip)
1920
+ local lch = MiniColors.convert(hex, 'oklch')
1921
+ if lch.h == nil then return hex end
1922
+
1923
+ -- Pressure is a circular distance to 180 hue degrees
1924
+ -- Output value will lie in the same horizontal half plane
1925
+ local is_up = 0 <= lch.h and lch.h < 180
1926
+ local press = is_up and (180 - lch.h) or (lch.h - 180)
1927
+ local new_press = H.clip(f(press), 0, 180)
1928
+ lch.h = is_up and (180 - new_press) or (new_press + 180)
1929
+
1930
+ return MiniColors.convert(lch, 'hex', { gamut_clip = gamut_clip })
1931
+ end
1932
+
1933
+ H.channel_modifiers.a = function(hex, f, gamut_clip)
1934
+ local lab = MiniColors.convert(hex, 'oklab')
1935
+ lab.a = f(lab.a)
1936
+ return MiniColors.convert(lab, 'hex', { gamut_clip = gamut_clip })
1937
+ end
1938
+
1939
+ H.channel_modifiers.b = function(hex, f, gamut_clip)
1940
+ local lab = MiniColors.convert(hex, 'oklab')
1941
+ lab.b = f(lab.b)
1942
+ return MiniColors.convert(lab, 'hex', { gamut_clip = gamut_clip })
1943
+ end
1944
+
1945
+ H.channel_modifiers.red = function(hex, f, gamut_clip)
1946
+ local rgb = H.hex2rgb(hex)
1947
+ rgb.r = H.clip(f(rgb.r), 0, 255)
1948
+ return MiniColors.convert(rgb, 'hex', { gamut_clip = gamut_clip })
1949
+ end
1950
+
1951
+ H.channel_modifiers.green = function(hex, f, gamut_clip)
1952
+ local rgb = H.hex2rgb(hex)
1953
+ rgb.g = H.clip(f(rgb.g), 0, 255)
1954
+ return MiniColors.convert(rgb, 'hex', { gamut_clip = gamut_clip })
1955
+ end
1956
+
1957
+ H.channel_modifiers.blue = function(hex, f, gamut_clip)
1958
+ local rgb = H.hex2rgb(hex)
1959
+ rgb.b = H.clip(f(rgb.b), 0, 255)
1960
+ return MiniColors.convert(rgb, 'hex', { gamut_clip = gamut_clip })
1961
+ end
1962
+
1963
+ -- Channel invert -------------------------------------------------------------
1964
+ --stylua: ignore
1965
+ H.chan_inverters = {
1966
+ lightness = function(x) return 100 - x end,
1967
+ -- chroma is the same as saturation
1968
+ saturation = function(x) return 100 - x end,
1969
+ hue = function(x) return 360 - x end,
1970
+ temperature = function(x) return 180 - x end,
1971
+ pressure = function(x) return 180 - x end,
1972
+ a = function(x) return -x end,
1973
+ b = function(x) return -x end,
1974
+ red = function(x) return 255-x end,
1975
+ green = function(x) return 255-x end,
1976
+ blue = function(x) return 255-x end,
1977
+ }
1978
+
1979
+ -- Channel repel --------------------------------------------------------------
1980
+ -- Use tie breakers when target is close to source
1981
+ H.repel_tie_breakers = {
1982
+ lightness = 50,
1983
+ chroma = 100,
1984
+ saturation = 50,
1985
+ hue = 180,
1986
+ temperature = 90,
1987
+ pressure = 90,
1988
+ a = 0,
1989
+ b = 0,
1990
+ red = 127,
1991
+ green = 127,
1992
+ blue = 127,
1993
+ }
1994
+
1995
+ H.nudge_repel = function(d, coef)
1996
+ -- Repel nudge will be added to distance from point to source.
1997
+ -- Ideas behind approach:
1998
+ -- - Nudge at `d = 0` should be equal to `coef`.
1999
+ -- - Nudge should monotonically decrease to 0 as distance tends to infinity.
2000
+ -- - The `d + nudge(d)` (distance after adding nudge) should be still
2001
+ -- monotonically increasing as to preserve order of repelled points.
2002
+ return coef * math.exp(-d / coef)
2003
+ end
2004
+
2005
+ H.nudge_attract = function(d, coef)
2006
+ -- Repel nudge will be added to distance from point to source.
2007
+ -- Ideas behind approach:
2008
+ -- - Adding nudge when `0 <= d <= coef` should lead to 0. This results into all
2009
+ -- points from `coef` neighborhood of source collapse into source.
2010
+ -- - Nudge should monotonically decrease to 0 as distance tends to infinity.
2011
+ -- - The `d + nudge(d)` (distance after adding nudge) should be still
2012
+ -- monotonically increasing as to preserve order of repelled points.
2013
+ return d <= coef and -d or (-coef * math.exp(1 - d / coef))
2014
+ end
2015
+
2016
+ H.repel = function(x, sources, coef, tie_breaker)
2017
+ if coef == 0 then return x end
2018
+
2019
+ local nudge = coef > 0 and H.nudge_repel or H.nudge_attract
2020
+ coef = math.abs(coef)
2021
+
2022
+ -- Use direction **towards** `tie_breaker` if `x` is very close to source
2023
+ -- to allow more useful repelling from sources on channel allowed edges.
2024
+ -- Example: both `repel(0, { 0 }, 10)` and `repel(100, { 100 }, 10)` should
2025
+ -- result into repelling **inside** [0; 100] resulting into 10 and 90.
2026
+ local tie_breaker_sign = tie_breaker < x and -1 or 1
2027
+
2028
+ local res = x
2029
+ for _, src in ipairs(sources) do
2030
+ -- Determine which way to move based on how `x` and source are positioned:
2031
+ -- - Towards tie breaker if tie.
2032
+ -- - To left if `x` is on left of source (and `coef > 0`).
2033
+ -- - To right if `x` is on right of source (and `coef > 0`).
2034
+ is_tie = math.abs(x - src) < 1e-4
2035
+ dir_sign = is_tie and tie_breaker_sign or (x < src and -1 or 1)
2036
+
2037
+ -- Using regular distance is a proper choice even for "hue" channel because
2038
+ -- it allows correct computation of nudge. Its periodic nature is accounted
2039
+ -- by prior adjustment of `sources` (adding periodic ones)
2040
+ res = res + dir_sign * nudge(H.dist(x, src), coef)
2041
+ end
2042
+ return res
2043
+ end
2044
+
2045
+ H.add_circle_sources = function(sources)
2046
+ local res = {}
2047
+ -- Adding two new sources in periodic fashion makes repel more periodic
2048
+ for _, src in ipairs(sources) do
2049
+ table.insert(res, src)
2050
+ table.insert(res, src - 360)
2051
+ table.insert(res, src + 360)
2052
+ end
2053
+ return res
2054
+ end
2055
+
2056
+ -- Color conversion -----------------------------------------------------------
2057
+ H.converters = {}
2058
+
2059
+ H.converters['8-bit'] = function(x, _, _) return H.get_closest_color_id(x, H.compute_term_oklab()) end
2060
+
2061
+ H.converters.hex = function(x, from_space, opts)
2062
+ if from_space == 'hex' then return x end
2063
+ return H.rgb2hex(MiniColors.convert(x, 'rgb', opts))
2064
+ end
2065
+
2066
+ H.converters.rgb = function(x, from_space, opts)
2067
+ if from_space == '8-bit' then
2068
+ local rgb = H.oklab2rgb(H.compute_term_oklab()[x])
2069
+ return vim.tbl_map(H.round, rgb)
2070
+ end
2071
+ if from_space == 'hex' then return H.hex2rgb(x) end
2072
+
2073
+ if from_space == 'rgb' then return { r = H.clip(x.r, 0, 255), g = H.clip(x.g, 0, 255), b = H.clip(x.b, 0, 255) } end
2074
+
2075
+ -- Clip non-gray color to be in gamut
2076
+ local lch = MiniColors.convert(x, 'oklch', opts)
2077
+ if lch.h ~= nil then lch = H.clip_to_gamut(lch, opts.gamut_clip) end
2078
+
2079
+ return H.oklab2rgb(H.oklch2oklab(lch))
2080
+ end
2081
+
2082
+ H.converters.oklab = function(x, from_space, opts) return H.oklch2oklab(MiniColors.convert(x, 'oklch', opts)) end
2083
+
2084
+ H.converters.oklch = function(x, from_space, opts)
2085
+ local res = nil
2086
+ if from_space == '8-bit' then res = H.oklab2oklch(H.compute_term_oklab()[x]) end
2087
+ if from_space == 'hex' then res = H.oklab2oklch(H.rgb2oklab(H.hex2rgb(x))) end
2088
+ if from_space == 'rgb' then res = H.oklab2oklch(H.rgb2oklab(x)) end
2089
+ if from_space == 'oklab' then res = H.oklab2oklch(x) end
2090
+ if from_space == 'oklch' then res = x end
2091
+ if from_space == 'okhsl' then res = H.okhsl2oklch(x) end
2092
+
2093
+ -- Normalize
2094
+ res.l = H.clip(res.l, 0, 100)
2095
+
2096
+ -- - Deal with grays separately
2097
+ if res.c <= 0 or res.h == nil then
2098
+ res.c, res.h = 0, nil
2099
+ else
2100
+ res.c, res.h = H.clip(res.c, 0, 100), res.h % 360
2101
+ end
2102
+
2103
+ return res
2104
+ end
2105
+
2106
+ H.converters.okhsl = function(x, from_space, opts) return H.oklch2okhsl(MiniColors.convert(x, 'oklch', opts)) end
2107
+
2108
+ H.infer_color_space = function(x)
2109
+ if type(x) == 'number' and 16 <= x and x <= 255 then return '8-bit' end
2110
+ if type(x) == 'string' and x:find('^#%x%x%x%x%x%x$') ~= nil then return 'hex' end
2111
+
2112
+ local err_msg = 'Can not infer color space of ' .. vim.inspect(x)
2113
+ if type(x) ~= 'table' then H.error(err_msg) end
2114
+
2115
+ local is_num = H.is_number
2116
+ if is_num(x.l) then
2117
+ if is_num(x.c) then return 'oklch' end
2118
+ if is_num(x.a) and is_num(x.b) then return 'oklab' end
2119
+ if is_num(x.s) then return 'okhsl' end
2120
+ end
2121
+
2122
+ if is_num(x.r) and is_num(x.g) and is_num(x.b) then return 'rgb' end
2123
+
2124
+ H.error(err_msg)
2125
+ end
2126
+
2127
+ -- HEX <-> RGB in [0; 255]
2128
+ H.hex2rgb = function(hex)
2129
+ local dec = tonumber(hex:sub(2), 16)
2130
+
2131
+ local b = math.fmod(dec, 256)
2132
+ local g = math.fmod((dec - b) / 256, 256)
2133
+ local r = math.floor(dec / 65536)
2134
+
2135
+ return { r = r, g = g, b = b }
2136
+ end
2137
+
2138
+ H.rgb2hex = function(rgb)
2139
+ -- Use straightforward clipping to [0; 255] here to ensure correctness.
2140
+ -- Modify `rgb` prior to this to ensure only a small distortion.
2141
+ local r = H.clip(H.round(rgb.r), 0, 255)
2142
+ local g = H.clip(H.round(rgb.g), 0, 255)
2143
+ local b = H.clip(H.round(rgb.b), 0, 255)
2144
+
2145
+ return string.format('#%02x%02x%02x', r, g, b)
2146
+ end
2147
+
2148
+ -- Sources for Oklab/Oklch:
2149
+ -- https://github.com/bottosson/bottosson.github.io/blob/master/misc/colorpicker/colorconversion.js
2150
+ -- https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
2151
+ --
2152
+ -- Okhsl is a local variant of Oklch with `s` for "saturation" - percent of
2153
+ -- chroma relative to maximum possible chroma for this lightness and hue.
2154
+ --
2155
+ -- NOTEs:
2156
+ -- - Coordinates ranges: `l` - [0; 100], `a`/`b` - no range, `c` - [0; 100]
2157
+ -- (way less in gamut), `s` - [0; 100], `h` - [0; 360).
2158
+ -- - Lightness is always assumed to be corrected
2159
+
2160
+ -- RGB in [0; 255] <-> Oklab
2161
+ -- https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
2162
+ H.rgb2oklab = function(rgb)
2163
+ -- Convert to linear RGB
2164
+ local r, g, b = H.correct_channel(rgb.r / 255), H.correct_channel(rgb.g / 255), H.correct_channel(rgb.b / 255)
2165
+
2166
+ -- Convert to Oklab
2167
+ local l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b
2168
+ local m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b
2169
+ local s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b
2170
+
2171
+ local l_, m_, s_ = H.cuberoot(l), H.cuberoot(m), H.cuberoot(s)
2172
+
2173
+ local L = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_
2174
+ local A = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_
2175
+ local B = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_
2176
+
2177
+ -- Explicitly convert for nearly achromatic colors
2178
+ if math.abs(A) < 1e-4 then A = 0 end
2179
+ if math.abs(B) < 1e-4 then B = 0 end
2180
+
2181
+ -- Normalize to appropriate range
2182
+ return { l = H.correct_lightness(100 * L), a = 100 * A, b = 100 * B }
2183
+ end
2184
+
2185
+ H.oklab2rgb = function(lab)
2186
+ local L, A, B = 0.01 * H.correct_lightness_inv(lab.l), 0.01 * lab.a, 0.01 * lab.b
2187
+
2188
+ local l_ = L + 0.3963377774 * A + 0.2158037573 * B
2189
+ local m_ = L - 0.1055613458 * A - 0.0638541728 * B
2190
+ local s_ = L - 0.0894841775 * A - 1.2914855480 * B
2191
+
2192
+ local l = l_ * l_ * l_
2193
+ local m = m_ * m_ * m_
2194
+ local s = s_ * s_ * s_
2195
+
2196
+ --stylua: ignore
2197
+ local r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s
2198
+ local g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s
2199
+ local b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s
2200
+
2201
+ return { r = 255 * H.correct_channel_inv(r), g = 255 * H.correct_channel_inv(g), b = 255 * H.correct_channel_inv(b) }
2202
+ end
2203
+
2204
+ -- Oklab <-> Oklch
2205
+ H.oklab2oklch = function(lab)
2206
+ local c = math.sqrt(lab.a ^ 2 + lab.b ^ 2)
2207
+ -- Treat grays specially
2208
+ local h = nil
2209
+ if c > 0 then h = H.rad2degree(math.atan2(lab.b, lab.a)) end
2210
+ return { l = lab.l, c = c, h = h }
2211
+ end
2212
+
2213
+ H.oklch2oklab = function(lch)
2214
+ -- Treat grays specially
2215
+ if lch.c <= 0 or lch.h == nil then return { l = lch.l, a = 0, b = 0 } end
2216
+
2217
+ local a = lch.c * math.cos(H.degree2rad(lch.h))
2218
+ local b = lch.c * math.sin(H.degree2rad(lch.h))
2219
+ return { l = lch.l, a = a, b = b }
2220
+ end
2221
+
2222
+ -- Oklch <-> Okhsl
2223
+ H.oklch2okhsl = function(lch)
2224
+ if lch.c <= 0 or lch.h == nil then return { l = lch.l, s = 0 } end
2225
+
2226
+ local gamut_points = H.get_gamut_points(lch)
2227
+ local percent = 100 * lch.c / gamut_points.c_upper
2228
+
2229
+ return { l = lch.l, s = H.clip(percent, 0, 100), h = lch.h }
2230
+ end
2231
+
2232
+ H.okhsl2oklch = function(lsh)
2233
+ if lsh.s <= 0 or lsh.h == nil then return { l = lsh.l, c = 0 } end
2234
+
2235
+ local gamut_points = H.get_gamut_points(lsh)
2236
+ local c = 0.01 * lsh.s * gamut_points.c_upper
2237
+
2238
+ return { l = lsh.l, c = H.clip(c, 0, math.huge), h = lsh.h }
2239
+ end
2240
+
2241
+ -- Degree in [0; 360] <-> Radian in [0; 2*pi]
2242
+ H.rad2degree = function(x) return (x % H.tau) * 360 / H.tau end
2243
+
2244
+ H.degree2rad = function(x) return (x % 360) * H.tau / 360 end
2245
+
2246
+ -- Functions for RGB channel correction. Assumes input in [0; 1] range
2247
+ -- https://bottosson.github.io/posts/colorwrong/#what-can-we-do%3F
2248
+ H.correct_channel = function(x) return 0.04045 < x and math.pow((x + 0.055) / 1.055, 2.4) or (x / 12.92) end
2249
+
2250
+ H.correct_channel_inv = function(x)
2251
+ return (0.0031308 >= x) and (12.92 * x) or (1.055 * math.pow(x, 0.416666667) - 0.055)
2252
+ end
2253
+
2254
+ -- Functions for lightness correction
2255
+ -- https://bottosson.github.io/posts/colorpicker/#intermission---a-new-lightness-estimate-for-oklab
2256
+ H.correct_lightness = function(x)
2257
+ if not H.adjust_lightness then return x end
2258
+
2259
+ x = 0.01 * x
2260
+ local k1, k2 = 0.206, 0.03
2261
+ local k3 = (1 + k1) / (1 + k2)
2262
+
2263
+ local res = 0.5 * (k3 * x - k1 + math.sqrt((k3 * x - k1) ^ 2 + 4 * k2 * k3 * x))
2264
+ return 100 * res
2265
+ end
2266
+
2267
+ H.correct_lightness_inv = function(x)
2268
+ if not H.adjust_lightness then return x end
2269
+
2270
+ x = 0.01 * x
2271
+ local k1, k2 = 0.206, 0.03
2272
+ local k3 = (1 + k1) / (1 + k2)
2273
+ local res = (x / k3) * (x + k1) / (x + k2)
2274
+ return 100 * res
2275
+ end
2276
+
2277
+ -- Get gamut ranges for Lch point. They are computed for its hue leaf as
2278
+ -- segments of triangle in (c, l) coordinates ((0, 0), (0, 100), cusp).
2279
+ -- Equations for triangle parts:
2280
+ -- - Lower segment ((0; 0) to cusp): y * c_cusp = x * L_cusp
2281
+ -- - Upper segment ((0; 100) to cusp): (100 - y) * c_cusp = x * (100 - L_cusp)
2282
+ -- NOTEs:
2283
+ -- - It is **very important** that this triangle is computed for **not
2284
+ -- corrected** lightness. But it is assumed **corrected lightness** in input.
2285
+ -- - This approach is not entirely accurate and can results in ranges outside
2286
+ -- of input `lch` for in-gamut point. Put it should be pretty rare: ~0.5%
2287
+ -- cases for most saturated colors.
2288
+ H.get_gamut_points = function(lch)
2289
+ local c, l = lch.c, H.clip(lch.l, 0, 100)
2290
+ l = H.correct_lightness_inv(l)
2291
+ local cusp = H.cusps[math.floor(lch.h % 360)]
2292
+ local c_cusp, l_cusp = cusp[1], cusp[2]
2293
+
2294
+ -- Maximum allowed chroma is computed based on current lightness and depends
2295
+ -- on whether `l` is below or above cusp's `l`:
2296
+ -- - If below, then it is from lower triangle segment.
2297
+ -- - If above - from upper segment.
2298
+ local c_upper = l <= l_cusp and (c_cusp * l / l_cusp) or (c_cusp * (100 - l) / (100 - l_cusp))
2299
+ -- - Don't allow negative chroma (can happen if `l` is out of [0; 100])
2300
+ c_upper = H.clip(c_upper, 0, math.huge)
2301
+
2302
+ -- Other points can be computed only in presence of actual chroma
2303
+ if c == nil then return { c_upper = c_upper } end
2304
+
2305
+ -- Range of allowed lightness is computed based on current chroma:
2306
+ -- - Lower is from segment between (0, 0) and cusp.
2307
+ -- - Upper is from segment between (0, 100) and cusp.
2308
+ local l_lower, l_upper
2309
+ if c < 0 then
2310
+ l_lower, l_upper = 0, 100
2311
+ elseif c_cusp < c then
2312
+ l_lower, l_upper = l_cusp, l_cusp
2313
+ else
2314
+ local saturation = c / c_cusp
2315
+ l_lower = saturation * l_cusp
2316
+ l_upper = saturation * (l_cusp - 100) + 100
2317
+ end
2318
+
2319
+ -- Intersection of segment between (c, l) and (0, l_cusp) with gamut boundary
2320
+ local c_cusp_clip, l_cusp_clip
2321
+ if c <= 0 then
2322
+ c_cusp_clip, l_cusp_clip = c, l
2323
+ elseif l <= l_cusp then
2324
+ -- Intersection with lower segment
2325
+ local prop = 1 - l / l_cusp
2326
+ c_cusp_clip = c_cusp * c / (c_cusp * prop + c)
2327
+ l_cusp_clip = l_cusp * c_cusp_clip / c_cusp
2328
+ else
2329
+ -- Intersection with upper segment
2330
+ local prop = 1 - (l - 100) / (l_cusp - 100)
2331
+ c_cusp_clip = c_cusp * c / (c_cusp * prop + c)
2332
+ l_cusp_clip = 100 + c_cusp_clip * (l_cusp - 100) / c_cusp
2333
+ end
2334
+
2335
+ return {
2336
+ l_lower = H.correct_lightness(l_lower),
2337
+ l_upper = H.correct_lightness(l_upper),
2338
+ c_upper = c_upper,
2339
+ l_cusp_clip = H.correct_lightness(l_cusp_clip),
2340
+ c_cusp_clip = c_cusp_clip,
2341
+ }
2342
+ end
2343
+
2344
+ H.clip_to_gamut = function(lch, gamut_clip)
2345
+ -- `lch` should have not corrected lightness
2346
+ local res = vim.deepcopy(lch)
2347
+ local gamut_points = H.get_gamut_points(lch)
2348
+
2349
+ local is_inside_gamut = lch.c <= gamut_points.c_upper
2350
+ if is_inside_gamut then return res end
2351
+
2352
+ -- Clip by going towards (0, l_cusp) until in gamut. This approach proved to
2353
+ -- be the best because of reasonable compromise between chroma and lightness.
2354
+ -- In particular when inverting lightness of dark color schemes:
2355
+ -- - Clipping by reducing chroma with constant lightness leads to a dark
2356
+ -- foreground with hardly distinguishable colors.
2357
+ -- - Clipping by adjusting lightness with constant chroma leads to very low
2358
+ -- contrast on a particularly saturated foreground colors.
2359
+ if gamut_clip == 'cusp' then
2360
+ res.l, res.c = gamut_points.l_cusp_clip, gamut_points.c_cusp_clip
2361
+ end
2362
+
2363
+ -- Preserve lightness by clipping chroma
2364
+ if gamut_clip == 'chroma' then res.c = H.clip(res.c, 0, gamut_points.c_upper) end
2365
+
2366
+ -- Preserve chroma by clipping lightness
2367
+ if gamut_clip == 'lightness' then res.l = H.clip(res.l, gamut_points.l_lower, gamut_points.l_upper) end
2368
+
2369
+ return res
2370
+ end
2371
+
2372
+ -- Interactive ----------------------------------------------------------------
2373
+ H.apply_interactive_buffer = function(buf_id, init_cs)
2374
+ -- Create temporary color scheme
2375
+ _G._interactive_cs = vim.deepcopy(init_cs)
2376
+
2377
+ -- Create initial script lines exposing color scheme and its methods
2378
+ local lines = { '-- Source code for interactive buffer', 'local self = _G._interactive_cs' }
2379
+ for key, val in pairs(_G._interactive_cs) do
2380
+ if vim.is_callable(val) then
2381
+ local l = string.format('local %s = function(...) self = self:%s(...) end', key, key)
2382
+ table.insert(lines, l)
2383
+ end
2384
+ end
2385
+
2386
+ -- Add current lines
2387
+ lines = vim.list_extend(lines, vim.api.nvim_buf_get_lines(buf_id, 0, -1, true))
2388
+
2389
+ -- Return final result
2390
+ table.insert(lines, 'return self')
2391
+
2392
+ -- Source
2393
+ local ok, res = pcall(loadstring(table.concat(lines, '\n')))
2394
+ _G._interactive_cs = nil
2395
+
2396
+ if not ok then error(res) end
2397
+ return res
2398
+ end
2399
+
2400
+ -- Utilities ------------------------------------------------------------------
2401
+ H.error = function(msg) error('(mini.colors) ' .. msg, 0) end
2402
+
2403
+ H.check_type = function(name, val, ref, allow_nil)
2404
+ if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
2405
+ H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
2406
+ end
2407
+
2408
+ H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, 'minicolors://' .. buf_id .. '/' .. name) end
2409
+
2410
+ H.round = function(x)
2411
+ if x == nil then return nil end
2412
+ return math.floor(x + 0.5)
2413
+ end
2414
+
2415
+ H.clip = function(x, from, to) return math.min(math.max(x, from), to) end
2416
+
2417
+ H.cuberoot = function(x) return math.pow(x, 0.333333) end
2418
+
2419
+ H.dist = function(x, y) return math.abs(x - y) end
2420
+
2421
+ H.dist_circle = function(x, y)
2422
+ -- Respect gray colors which don't have hue
2423
+ if x == nil and y == nil then return 0 end
2424
+ if x == nil or y == nil then return math.huge end
2425
+
2426
+ local d = H.dist(x % 360, y % 360)
2427
+ return math.min(d, 360 - d)
2428
+ end
2429
+
2430
+ H.dist_oklab = function(x, y) return math.abs(x.l - y.l) + math.abs(x.a - y.a) + math.abs(x.b - y.b) end
2431
+
2432
+ H.convex_continuous = function(x, y, coef)
2433
+ if x == nil or y == nil then return H.convex_discrete(x, y, coef) end
2434
+ return H.round((1 - coef) * x + coef * y)
2435
+ end
2436
+
2437
+ H.convex_discrete = function(x, y, coef)
2438
+ -- Using `vim.deepcopy()` ensures no side effects
2439
+ if coef < 0.5 then return vim.deepcopy(x) end
2440
+ return vim.deepcopy(y)
2441
+ end
2442
+
2443
+ H.union = function(arr1, arr2)
2444
+ local value_is_present = {}
2445
+ for _, x in ipairs(arr1) do
2446
+ value_is_present[x] = true
2447
+ end
2448
+ for _, x in ipairs(arr2) do
2449
+ value_is_present[x] = true
2450
+ end
2451
+ return vim.tbl_keys(value_is_present)
2452
+ end
2453
+
2454
+ H.get_closest = function(x, values, dist_fun)
2455
+ local best_val, best_key, best_dist = nil, nil, math.huge
2456
+ for key, val in pairs(values) do
2457
+ local cur_dist = dist_fun(x, val)
2458
+ if cur_dist <= best_dist then
2459
+ best_val, best_key, best_dist = val, key, cur_dist
2460
+ end
2461
+ end
2462
+
2463
+ return best_val, best_key
2464
+ end
2465
+
2466
+ H.is_number = function(x) return type(x) == 'number' end
2467
+
2468
+ H.is_table = function(x) return type(x) == 'table' end
2469
+
2470
+ H.is_string = function(x) return type(x) == 'string' end
2471
+
2472
+ H.all = function(arr, predicate)
2473
+ predicate = predicate or function(x) return x end
2474
+ for _, x in pairs(arr) do
2475
+ if not predicate(x) then return false end
2476
+ end
2477
+ return true
2478
+ end
2479
+
2480
+ -- TODO: Remove after compatibility with Neovim=0.9 is dropped
2481
+ H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
2482
+
2483
+ return MiniColors