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.
- package/.conda/retrovim.yaml +259 -0
- package/.conda/terax.icns +0 -0
- package/.conda/terax.ico +0 -0
- package/.conda/terax.png +0 -0
- package/.github/workflows/release.yml +95 -0
- package/.gitmodules +3 -0
- package/.npm/kanata_touchcursor.js +28 -0
- package/.npm/nvim.js +26 -0
- package/.npm/yazi.js +26 -0
- package/.npm/zsh.js +17 -0
- package/.pip/kanata_touchcursor/__init__.py +28 -0
- package/.pip/nvim/__init__.py +31 -0
- package/.pip/yazi/__init__.py +31 -0
- package/.pip/zsh/__init__.py +22 -0
- package/CHANGELOG.md +26 -0
- package/LICENSE +674 -0
- package/README.md +336 -0
- package/kanata/touchcursor.kbd +164 -0
- package/nvim/init.lua +813 -0
- package/nvim/plugins/mini.nvim/.github/DISCUSSION_TEMPLATE/q-a.yml +72 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/bug-report.yml +136 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/feature-request.yml +75 -0
- package/nvim/plugins/mini.nvim/.github/PULL_REQUEST_TEMPLATE.md +2 -0
- package/nvim/plugins/mini.nvim/.github/workflows/quality-control.yml +150 -0
- package/nvim/plugins/mini.nvim/.pre-commit-config.yaml +20 -0
- package/nvim/plugins/mini.nvim/.stylua.toml +7 -0
- package/nvim/plugins/mini.nvim/.styluaignore +1 -0
- package/nvim/plugins/mini.nvim/CHANGELOG.md +1894 -0
- package/nvim/plugins/mini.nvim/CODE_OF_CONDUCT.md +132 -0
- package/nvim/plugins/mini.nvim/CONTRIBUTING.md +331 -0
- package/nvim/plugins/mini.nvim/LICENSE +21 -0
- package/nvim/plugins/mini.nvim/MAINTAINING.md +330 -0
- package/nvim/plugins/mini.nvim/Makefile +51 -0
- package/nvim/plugins/mini.nvim/README.md +261 -0
- package/nvim/plugins/mini.nvim/TESTING.md +1011 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/README.md +35 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/benchmark.sh +56 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-alpha.lua +21 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-original.lua +17 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-starter.lua +18 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_empty.lua +4 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_starter-default.lua +7 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-alpha.lua +10 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-original.lua +7 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-starter.lua +20 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/install.sh +10 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/make_summary.py +68 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/startup-summary.md +10 -0
- package/nvim/plugins/mini.nvim/colors/miniautumn.lua +55 -0
- package/nvim/plugins/mini.nvim/colors/minicyan.lua +95 -0
- package/nvim/plugins/mini.nvim/colors/minischeme.lua +95 -0
- package/nvim/plugins/mini.nvim/colors/minispring.lua +53 -0
- package/nvim/plugins/mini.nvim/colors/minisummer.lua +49 -0
- package/nvim/plugins/mini.nvim/colors/miniwinter.lua +51 -0
- package/nvim/plugins/mini.nvim/colors/randomhue.lua +16 -0
- package/nvim/plugins/mini.nvim/doc/mini-ai.txt +889 -0
- package/nvim/plugins/mini.nvim/doc/mini-align.txt +923 -0
- package/nvim/plugins/mini.nvim/doc/mini-animate.txt +937 -0
- package/nvim/plugins/mini.nvim/doc/mini-base16.txt +285 -0
- package/nvim/plugins/mini.nvim/doc/mini-basics.txt +359 -0
- package/nvim/plugins/mini.nvim/doc/mini-bracketed.txt +622 -0
- package/nvim/plugins/mini.nvim/doc/mini-bufremove.txt +123 -0
- package/nvim/plugins/mini.nvim/doc/mini-clue.txt +760 -0
- package/nvim/plugins/mini.nvim/doc/mini-cmdline.txt +392 -0
- package/nvim/plugins/mini.nvim/doc/mini-colors.txt +880 -0
- package/nvim/plugins/mini.nvim/doc/mini-comment.txt +230 -0
- package/nvim/plugins/mini.nvim/doc/mini-completion.txt +477 -0
- package/nvim/plugins/mini.nvim/doc/mini-cursorword.txt +105 -0
- package/nvim/plugins/mini.nvim/doc/mini-deps.txt +590 -0
- package/nvim/plugins/mini.nvim/doc/mini-diff.txt +661 -0
- package/nvim/plugins/mini.nvim/doc/mini-doc.txt +434 -0
- package/nvim/plugins/mini.nvim/doc/mini-extra.txt +856 -0
- package/nvim/plugins/mini.nvim/doc/mini-files.txt +1018 -0
- package/nvim/plugins/mini.nvim/doc/mini-fuzzy.txt +160 -0
- package/nvim/plugins/mini.nvim/doc/mini-git.txt +425 -0
- package/nvim/plugins/mini.nvim/doc/mini-hipatterns.txt +489 -0
- package/nvim/plugins/mini.nvim/doc/mini-hues.txt +420 -0
- package/nvim/plugins/mini.nvim/doc/mini-icons.txt +508 -0
- package/nvim/plugins/mini.nvim/doc/mini-indentscope.txt +485 -0
- package/nvim/plugins/mini.nvim/doc/mini-input.txt +932 -0
- package/nvim/plugins/mini.nvim/doc/mini-jump.txt +202 -0
- package/nvim/plugins/mini.nvim/doc/mini-jump2d.txt +525 -0
- package/nvim/plugins/mini.nvim/doc/mini-keymap.txt +449 -0
- package/nvim/plugins/mini.nvim/doc/mini-map.txt +717 -0
- package/nvim/plugins/mini.nvim/doc/mini-misc.txt +431 -0
- package/nvim/plugins/mini.nvim/doc/mini-move.txt +181 -0
- package/nvim/plugins/mini.nvim/doc/mini-notify.txt +434 -0
- package/nvim/plugins/mini.nvim/doc/mini-nvim.txt +431 -0
- package/nvim/plugins/mini.nvim/doc/mini-operators.txt +472 -0
- package/nvim/plugins/mini.nvim/doc/mini-pairs.txt +346 -0
- package/nvim/plugins/mini.nvim/doc/mini-pick.txt +1535 -0
- package/nvim/plugins/mini.nvim/doc/mini-sessions.txt +239 -0
- package/nvim/plugins/mini.nvim/doc/mini-snippets.txt +1291 -0
- package/nvim/plugins/mini.nvim/doc/mini-splitjoin.txt +536 -0
- package/nvim/plugins/mini.nvim/doc/mini-starter.txt +618 -0
- package/nvim/plugins/mini.nvim/doc/mini-statusline.txt +361 -0
- package/nvim/plugins/mini.nvim/doc/mini-surround.txt +863 -0
- package/nvim/plugins/mini.nvim/doc/mini-tabline.txt +151 -0
- package/nvim/plugins/mini.nvim/doc/mini-test.txt +966 -0
- package/nvim/plugins/mini.nvim/doc/mini-trailspace.txt +91 -0
- package/nvim/plugins/mini.nvim/doc/mini-visits.txt +937 -0
- package/nvim/plugins/mini.nvim/logo.png +0 -0
- package/nvim/plugins/mini.nvim/lua/mini/ai.lua +2244 -0
- package/nvim/plugins/mini.nvim/lua/mini/align.lua +2076 -0
- package/nvim/plugins/mini.nvim/lua/mini/animate.lua +2127 -0
- package/nvim/plugins/mini.nvim/lua/mini/base16.lua +1752 -0
- package/nvim/plugins/mini.nvim/lua/mini/basics.lua +772 -0
- package/nvim/plugins/mini.nvim/lua/mini/bracketed.lua +2013 -0
- package/nvim/plugins/mini.nvim/lua/mini/bufremove.lua +283 -0
- package/nvim/plugins/mini.nvim/lua/mini/clue.lua +2096 -0
- package/nvim/plugins/mini.nvim/lua/mini/cmdline.lua +1182 -0
- package/nvim/plugins/mini.nvim/lua/mini/colors.lua +2483 -0
- package/nvim/plugins/mini.nvim/lua/mini/comment.lua +586 -0
- package/nvim/plugins/mini.nvim/lua/mini/completion.lua +2094 -0
- package/nvim/plugins/mini.nvim/lua/mini/cursorword.lua +311 -0
- package/nvim/plugins/mini.nvim/lua/mini/deps.lua +1660 -0
- package/nvim/plugins/mini.nvim/lua/mini/diff.lua +1938 -0
- package/nvim/plugins/mini.nvim/lua/mini/doc.lua +1371 -0
- package/nvim/plugins/mini.nvim/lua/mini/extra.lua +2290 -0
- package/nvim/plugins/mini.nvim/lua/mini/files.lua +3145 -0
- package/nvim/plugins/mini.nvim/lua/mini/fuzzy.lua +356 -0
- package/nvim/plugins/mini.nvim/lua/mini/git.lua +1737 -0
- package/nvim/plugins/mini.nvim/lua/mini/hipatterns.lua +1042 -0
- package/nvim/plugins/mini.nvim/lua/mini/hues.lua +2088 -0
- package/nvim/plugins/mini.nvim/lua/mini/icons.lua +2254 -0
- package/nvim/plugins/mini.nvim/lua/mini/indentscope.lua +1158 -0
- package/nvim/plugins/mini.nvim/lua/mini/init.lua +427 -0
- package/nvim/plugins/mini.nvim/lua/mini/input.lua +2183 -0
- package/nvim/plugins/mini.nvim/lua/mini/jump.lua +609 -0
- package/nvim/plugins/mini.nvim/lua/mini/jump2d.lua +1244 -0
- package/nvim/plugins/mini.nvim/lua/mini/keymap.lua +880 -0
- package/nvim/plugins/mini.nvim/lua/mini/map.lua +1709 -0
- package/nvim/plugins/mini.nvim/lua/mini/misc.lua +969 -0
- package/nvim/plugins/mini.nvim/lua/mini/move.lua +509 -0
- package/nvim/plugins/mini.nvim/lua/mini/notify.lua +938 -0
- package/nvim/plugins/mini.nvim/lua/mini/operators.lua +1349 -0
- package/nvim/plugins/mini.nvim/lua/mini/pairs.lua +657 -0
- package/nvim/plugins/mini.nvim/lua/mini/pick.lua +3876 -0
- package/nvim/plugins/mini.nvim/lua/mini/sessions.lua +689 -0
- package/nvim/plugins/mini.nvim/lua/mini/snippets.lua +2828 -0
- package/nvim/plugins/mini.nvim/lua/mini/splitjoin.lua +1132 -0
- package/nvim/plugins/mini.nvim/lua/mini/starter.lua +1599 -0
- package/nvim/plugins/mini.nvim/lua/mini/statusline.lua +686 -0
- package/nvim/plugins/mini.nvim/lua/mini/surround.lua +2388 -0
- package/nvim/plugins/mini.nvim/lua/mini/tabline.lua +564 -0
- package/nvim/plugins/mini.nvim/lua/mini/test.lua +2516 -0
- package/nvim/plugins/mini.nvim/lua/mini/trailspace.lua +215 -0
- package/nvim/plugins/mini.nvim/lua/mini/visits.lua +1577 -0
- package/nvim/plugins/mini.nvim/readmes/mini-ai.md +190 -0
- package/nvim/plugins/mini.nvim/readmes/mini-align.md +203 -0
- package/nvim/plugins/mini.nvim/readmes/mini-animate.md +214 -0
- package/nvim/plugins/mini.nvim/readmes/mini-base16.md +198 -0
- package/nvim/plugins/mini.nvim/readmes/mini-basics.md +184 -0
- package/nvim/plugins/mini.nvim/readmes/mini-bracketed.md +193 -0
- package/nvim/plugins/mini.nvim/readmes/mini-bufremove.md +139 -0
- package/nvim/plugins/mini.nvim/readmes/mini-clue.md +260 -0
- package/nvim/plugins/mini.nvim/readmes/mini-cmdline.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-colors.md +169 -0
- package/nvim/plugins/mini.nvim/readmes/mini-comment.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-completion.md +217 -0
- package/nvim/plugins/mini.nvim/readmes/mini-cursorword.md +137 -0
- package/nvim/plugins/mini.nvim/readmes/mini-deps.md +271 -0
- package/nvim/plugins/mini.nvim/readmes/mini-diff.md +289 -0
- package/nvim/plugins/mini.nvim/readmes/mini-doc.md +198 -0
- package/nvim/plugins/mini.nvim/readmes/mini-extra.md +158 -0
- package/nvim/plugins/mini.nvim/readmes/mini-files.md +247 -0
- package/nvim/plugins/mini.nvim/readmes/mini-fuzzy.md +134 -0
- package/nvim/plugins/mini.nvim/readmes/mini-git.md +168 -0
- package/nvim/plugins/mini.nvim/readmes/mini-hipatterns.md +176 -0
- package/nvim/plugins/mini.nvim/readmes/mini-hues.md +285 -0
- package/nvim/plugins/mini.nvim/readmes/mini-icons.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-indentscope.md +191 -0
- package/nvim/plugins/mini.nvim/readmes/mini-input.md +163 -0
- package/nvim/plugins/mini.nvim/readmes/mini-jump.md +161 -0
- package/nvim/plugins/mini.nvim/readmes/mini-jump2d.md +197 -0
- package/nvim/plugins/mini.nvim/readmes/mini-keymap.md +205 -0
- package/nvim/plugins/mini.nvim/readmes/mini-map.md +191 -0
- package/nvim/plugins/mini.nvim/readmes/mini-misc.md +145 -0
- package/nvim/plugins/mini.nvim/readmes/mini-move.md +163 -0
- package/nvim/plugins/mini.nvim/readmes/mini-notify.md +175 -0
- package/nvim/plugins/mini.nvim/readmes/mini-operators.md +193 -0
- package/nvim/plugins/mini.nvim/readmes/mini-pairs.md +158 -0
- package/nvim/plugins/mini.nvim/readmes/mini-pick.md +340 -0
- package/nvim/plugins/mini.nvim/readmes/mini-sessions.md +164 -0
- package/nvim/plugins/mini.nvim/readmes/mini-snippets.md +373 -0
- package/nvim/plugins/mini.nvim/readmes/mini-splitjoin.md +194 -0
- package/nvim/plugins/mini.nvim/readmes/mini-starter.md +176 -0
- package/nvim/plugins/mini.nvim/readmes/mini-statusline.md +155 -0
- package/nvim/plugins/mini.nvim/readmes/mini-surround.md +190 -0
- package/nvim/plugins/mini.nvim/readmes/mini-tabline.md +156 -0
- package/nvim/plugins/mini.nvim/readmes/mini-test.md +175 -0
- package/nvim/plugins/mini.nvim/readmes/mini-trailspace.md +137 -0
- package/nvim/plugins/mini.nvim/readmes/mini-visits.md +243 -0
- package/nvim/plugins/mini.nvim/scripts/dual_log.sh +13 -0
- package/nvim/plugins/mini.nvim/scripts/dual_push.sh +15 -0
- package/nvim/plugins/mini.nvim/scripts/dual_release.sh +102 -0
- package/nvim/plugins/mini.nvim/scripts/dual_sync.sh +115 -0
- package/nvim/plugins/mini.nvim/scripts/init-deps-example.lua +59 -0
- package/nvim/plugins/mini.nvim/scripts/lint-filename-length.sh +16 -0
- package/nvim/plugins/mini.nvim/scripts/lintcommit-ci.sh +18 -0
- package/nvim/plugins/mini.nvim/scripts/lintcommit.lua +393 -0
- package/nvim/plugins/mini.nvim/scripts/minidoc.lua +57 -0
- package/nvim/plugins/mini.nvim/scripts/minimal_init.lua +28 -0
- package/nvim/plugins/mini.nvim/scripts/minitest.lua +4 -0
- package/package.json +16 -0
- package/pyproject.toml +43 -0
- package/yazi/init.lua +12 -0
- package/yazi/keymap.toml +114 -0
- package/yazi/lesskey +5 -0
- package/yazi/plugins/bookmarks.yazi/main.lua +86 -0
- package/yazi/plugins/cd-git-root.yazi/main.lua +31 -0
- package/yazi/plugins/smart-enter.yazi/main.lua +11 -0
- package/yazi/theme.toml +26 -0
- package/yazi/yazi.toml +15 -0
- package/zsh/.zshrc +145 -0
- package/zsh/starship.toml +20 -0
- 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
|