forked from nvim-mini/mini.nvim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcolors.lua
More file actions
2474 lines (2167 loc) · 97.4 KB
/
colors.lua
File metadata and controls
2474 lines (2167 loc) · 97.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
--- *mini.colors* Tweak and save any color scheme
---
--- MIT License Copyright (c) 2023 Evgeni Chasnovski
--- Features:
--- - Create colorscheme object: either manually (|MiniColors.as_colorscheme()|)
--- or by querying present color schemes (including currently active one; see
--- |MiniColors.get_colorscheme()|).
---
--- - Infer data about color scheme and/or modify based on it:
--- - Add transparency by removing background color (requires transparency
--- in terminal emulator).
--- - Infer cterm attributes (|cterm-colors|) based on gui colors making it
--- compatible with 'notermguicolors'.
--- - Resolve highlight group links (|:highlight-link|).
--- - Compress by removing redundant highlight groups.
--- - Extract palette of used colors and/or infer terminal colors
--- (|terminal-config|) based on it.
---
--- - Modify colors to better fit your taste and/or goals (see more in
--- |MiniColors-colorscheme-methods|):
--- - Apply any function to color hex string.
--- - Update channels (like lightness, saturation, hue, temperature, red,
--- green, blue, etc.; see more in |MiniColors-channels|).
--- Use either own function or one of the implemented methods:
--- - Add value to channel or multiply it by coefficient. Like "add 10
--- to saturation of every color" or "multiply saturation by 2" to
--- make colors more saturated (less gray).
--- - Invert. Like "invert lightness" to convert between dark/light theme.
--- - Set to one or more values (picks closest to current one). Like
--- "set to one or two hues" to make mono- or dichromatic color scheme.
--- - Repel from certain source(s) with stronger effect for closer values.
--- Like "repel from hue 30" to remove red color from color scheme.
--- Repel hue (how much is removed) is configurable.
--- - Simulate color vision deficiency.
---
--- - Once color scheme is ready, either apply it to see effects right away or
--- write it into a Lua file as a fully functioning separate color scheme.
---
--- - Experiment interactively with a feedback (|MiniColors.interactive()|).
---
--- - Animate transition between color schemes either with |MiniColors.animate()|
--- or with |:Colorscheme| user command.
---
--- - Convert within supported color spaces (|MiniColors.convert()|):
--- - Hex string.
--- - 8-bit number (terminal colors).
--- - RGB.
--- - Oklab, Oklch, Okhsl (https://bottosson.github.io/posts/oklab/).
---
--- Notes:
--- - There is a collection of |MiniColors-recipes| with code snippets for some
--- common tasks.
--- - There is no goal to support as many color spaces as possible, only the
--- already present ones.
---
--- # Tweak quick start ~
---
--- - Execute `:lua require('mini.colors').interactive()`.
---
--- - Experiment by writing calls to exposed color scheme methods and applying
--- them with `<M-a>`. For more information, see |MiniColors-colorscheme-methods|
--- and |MiniColors-recipes|.
---
--- - If you are happy with result, write color scheme with `<M-w>`. If not,
--- reset to initial color scheme with `<M-r>`.
---
--- - If only some highlight groups can be made better, adjust them manually
--- inside written color scheme file.
---
--- # Setup ~
---
--- This module doesn't need setup, but it can be done to improve usability.
--- Setup with `require('mini.colors').setup({})` (replace `{}` with your
--- `config` table). It will create global Lua table `MiniColors` which you can
--- use for scripting or manually (with `:lua MiniColors.*`).
---
--- See |MiniColors.config| for `config` structure and default values.
---
--- This module doesn't have runtime options, so using `vim.b.minicolors_config`
--- will have no effect here.
---
--- # Comparisons ~
---
--- - [rktjmp/lush.nvim](https://github.com/rktjmp/lush.nvim):
--- - Oriented towards tweaking separate highlight groups, while 'mini.colors'
--- is more designed to work with color scheme as a whole.
--- - Uses HSL and HSLuv color spaces, while 'mini.colors' uses Oklab, Oklch,
--- and Okhsl which have slightly better perceptual uniformity properties.
--- - Doesn't have functionality to infer and repair missing data in color
--- scheme (like cterm attributes, terminal colors, transparency, etc.),
--- while 'mini.colors' does.
--- - Doesn't implement animation of color scheme transition, while
--- 'mini.colors' does.
--- - [lifepillar/vim-colortemplate](https://github.com/lifepillar/vim-colortemplate):
--- - Comparisons are similar to that of 'rktjmp/lush.nvim'.
--- - [tjdevries/colorbuddy.nvim](https://github.com/tjdevries/colorbuddy.nvim):
--- - Comparisons are similar to that of 'rktjmp/lush.nvim'.
---@tag MiniColors
--- All following code snippets assume to be executed inside interactive buffer
--- (|MiniColors.interactive()|). They are directly copy-pasteable.
---
--- To apply single method to current color scheme, use >vim
--- :lua MiniColors.get_colorscheme():<method goes here>:apply().
--- <
--- Recipes:
--- - Tweak lightness: >lua
---
--- -- Invert dark/light color scheme to be light/dark
--- chan_invert('lightness', { gamut_clip = 'cusp' })
---
--- -- Ensure constant contrast ratio
--- chan_set('lightness', 15, { filter = 'bg' })
--- chan_set('lightness', 85, { filter = 'fg' })
--- <
--- - Tweak saturation: >lua
---
--- -- Make background colors less saturated and foreground - more
--- chan_add('saturation', -20, { filter = 'bg' })
--- chan_add('saturation', 20, { filter = 'fg' })
---
--- -- Convert to grayscale
--- chan_set('saturation', 0)
--- <
--- - Tweak hue: >lua
---
--- -- Create monochromatic variant (this uses green color)
--- chan_set('hue', 135)
---
--- -- Create dichromatic variant (this uses Neovim-themed hues)
--- chan_set('hue', { 140, 245 })
--- <
--- - Tweak temperature: >lua
---
--- -- Invert temperature (make cold theme become warm and vice versa)
--- chan_invert('temperature')
---
--- -- Make background colors colder and foreground warmer
--- chan_add('temperature', -40, { filter = 'bg' })
--- chan_add('temperature', 40, { filter = 'fg' })
--- <
--- - Counter color vision deficiency (try combinations of these to see which
--- one works best for you):
---
--- - Improve text saturation contrast (usually the best starting approach): >lua
---
--- chan_set('saturation', { 10, 90 }, { filter = 'fg' })
--- <
--- - Remove certain hues from all colors (use 30 for red, 90 for yellow,
--- 135 for green, 270 for blue): >lua
---
--- -- Repel red color
--- chan_repel('hue', 30, 45)
--- <
--- - Force equally spaced palette (remove ones with which you know you
--- have trouble): >lua
---
--- -- Might be a good choice for red-green color blindness
--- chan_set('hue', { 90, 180, 270})
---
--- -- Might be a good choice for blue-yellow color blindness
--- chan_set('hue', { 0, 90, 180 })
--- <
--- - Inverting temperature or pressure can sometimes improve readability: >lua
---
--- chan_invert('temperature')
--- chan_invert('pressure')
--- <
--- - If all hope is lost, hue random generation might help if you are lucky: >lua
---
--- chan_modify('hue', function() return math.random(0, 359) end)
--- <
--- - For color scheme creators use |MiniColors-colorscheme:simulate_cvd()| to
--- simulate various color vision deficiency types to see how color scheme
--- would look in the eyes of color blind person.
---@tag MiniColors-recipes
--- Color space is a way to quantitatively describe a color. In this module
--- color spaces are used both as source for |MiniColors-channels| and inputs
--- for |MiniColors.convert()|
---
--- List of supported color spaces (along with their id in parenthesis):
--- - 8-bit (`8-bit`) - integer between 16 and 255. Usually values 0-15 are also
--- supported, but they depend on terminal emulator theme which is not reliable.
--- See https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit .
---
--- - Hex (`hex`) - string of the form "#xxxxxx" where `x` is a hexadecimal number.
---
--- - RGB (`rgb`) - table with numeric fields `r` (red), `g` (green), `b` (blue).
--- Visible range is from 0 to 255.
---
--- - Oklab (`oklab`) - table with fields `l` (lightness; numeric in [0; 100]),
--- `a`, `b` (both are unbounded numeric; visible range is usually between
--- -30 to 30). Field `l` describes how light is color; `a` - how "green-red" it is;
--- `b` - how "blue-yellow" it is.
---
--- - Oklch (`oklch`) - table with fields `l` (same as in Oklab),
--- `c` (chroma; positive numeric, visible range usually lower than 32),
--- `h` (`nil` for grays or periodic numeric in [0, 360)). Field `c` describes how
--- colorful a color is; `h` is a value of "true color" on color circle/wheel.
--- NOTE: gray colors, being achromatic by nature, don't have hue.
---
--- - Okhsl (`okhsl`) - Oklch but with `c` replaced by `s` (saturation; numeric
--- in [0; 100]). Field `s` describes a percent of chroma relative to maximum
--- visible chroma for the particular lightness and hue combination. Note,
--- that mathematical model used to compute maximum visible chroma is
--- approximate which might lead to inaccuracies for highly saturated colors
--- with relatively low or high lightness.
---
--- Sources for Oklab/Oklch/Okhsl:
--- - https://bottosson.github.io/posts/oklab/ - initial derivation and
--- introduction of Oklab and Oklch.
--- - https://bottosson.github.io/misc/colorpicker - interactive color picker.
--- Great way for a hands-on introduction to concepts of lightness, chroma,
--- saturation, and hue.
---
--- Note that Oklab/Oklch/Okhsl use channel normalization for `l`, `a`, `b`, `c`, `s` that
--- is more oriented towards integer numbers (according to the above sources).
--- Some implementations (like in CSS) are more oriented towards [0; 1] range or
--- percentages. Adjust accordingly by dividing/multiplying output by 100.
--- Also use `adjust_lightness = false` in |MiniColors.convert()|.
---
--- # Gamut clip ~
--- *MiniColors-gamut-clip*
---
--- In Neovim highlight group colors are usually specified by their red, green,
--- and blue values from 0 to 255 in the form of HEX string (see |gui-colors|).
--- Although plenty, these are not all possible colors.
---
--- When performing color manipulation using |MiniColors-colorscheme-methods|,
--- it is possible to end up with "impossible" color (which can't be directly
--- converted to HEX string). For example, inverting lightness of color "#fce094"
--- will lead to a color `{ l = 10, c = 10, h = 90 }` in Oklch space, i.e.
--- "dark yellow" which is impossible to show in HEX.
---
--- **Gamut clipping** is an action of converting color outside of visible gamut
--- (colors representable with HEX string) to be inside it while preserving
--- certain perceptual characteristics as much as possible.
---
--- Gamut clipping in this module is done inside Oklch color space. The goal is to
--- preserve hue as much as possible while manipulating lightness and/or chroma.
---
--- List of supported gamut clip methods (along with their id in parenthesis):
--- - Clip chroma (`'chroma'`) - reduce chroma while preserving lightness until
--- color is inside visible gamut. Default method.
---
--- - Clip lightness (`'lightness'`) - reduce lightness while preserving chroma
--- until color is inside visible gamut.
---
--- - Clip according to "cusp" (`'cusp'`) - reduce both lightness and chroma in
--- a compromise way depending on hue.
--- Cusp is a color with the highest chroma inside slice of visible gamut
--- with the same hue (hue leaf). It is called that way because the slice has
--- a roughly triangular shape with points at (0, 0) - (0, 100) - "cusp" in
--- (chroma, lightness) coordinates.
--- Gamut clipping using "cusp" as reference is done by changing color towards
--- (0, cusp_lightness) point (gray with lightness equal to that of a current
--- cusp) until color is inside visible gamut.
---
--- In short:
--- - Usually `'chroma'` is enough.
--- - If colors are too desaturated - try `'cusp'`.
--- - If still not colorful enough - try `'lightness'`.
---
--- Notes:
--- - Currently implemented formulas are approximate (by design; to reduce code
--- complexity) so there might be problems for highly saturated colors with
--- relatively low or high lightness.
---@tag MiniColors-color-spaces
--- A color channel is a number describing one particular aspect of a color.
--- It is usually direct or modified coordinate of a color space. See
--- |MiniColors-color-spaces| for information on color spaces.
---
--- List of supported channels (along with their id in parenthesis):
--- - Lightness (`lightness`) - corrected `l` component of Oklch. Describes how
--- light is a color. Ranges from 0 (black dark) to 100 (white light).
---
--- - Chroma (`chroma`) - `c` component of Oklch. Describes how colorful is
--- a color in absolute units. Ranges from 0 (gray) to infinity (more like
--- around 30 in practice).
---
--- - Saturation (`saturation`) - `s` component of Okhsl. Describes how colorful
--- is color in relative units. Ranges from 0 (gray) to 100 (maximum saturation
--- for a given lightness-hue pair).
---
--- - Hue (`hue`) - `h` component of Oklch. Describes "true color value" (like
--- red/green/blue) as a number. It is a periodic value from 0 (included) to
--- 360 (not included). Best perceived as a degree on a color circle/wheel.
---
--- Approximate values for common color names:
--- - 0 - pink.
--- - 30 - red.
--- - 60 - orange.
--- - 90 - yellow.
--- - 135 - green.
--- - 180 - cyan.
--- - 225 - light blue.
--- - 270 - blue.
--- - 315 - magenta/purple.
---
--- - Temperature (`temperature`) - circular distance from current hue to hue 270
--- angle (blue). Ranges from 0 (cool) to 180 (hot) anchored at hues 270 (blue)
--- and 90 (yellow). Similar to `b` channel but tries to preserve chroma.
---
--- - Pressure (`pressure`) - circular distance from current hue to hue 180.
--- Ranges from 0 (low; green-ish) to 180 (high; red-ish) anchored at hues
--- 180 and 0. Similar to `a` channel but tries to preserve chroma.
--- Not widely used; added to have something similar to temperature.
---
--- - a (`a`) - `a` component of Oklab. Describes how "green-red" a color is.
--- Can have any value. Negative values are "green-ish", positive - "red-ish".
---
--- - b (`b`) - `b` component of Oklab. Describes how "blue-yellow" a color is.
--- Can have any value. Negative values are "blue-ish", positive - "yellow-ish".
---
--- - Red (`red`) - `r` component of RGB. Describes how much red a color has.
--- Ranges from 0 (no red) to 255 (full red).
---
--- - Green (`green`) - `g` component of RGB. Describes how much green a color has.
--- Ranges from 0 (no green) to 255 (full green).
---
--- - Blue (`blue`) - `b` component of RGB. Describes how much blue a color has.
--- Ranges from 0 (no blue) to 255 (full blue).
---@tag MiniColors-channels
---@alias __colors_channel `(string)` One of supported |MiniColors-channels|.
---@alias __colors_chan_opts `(table|nil)` Options. Possible fields:
--- - <filter> `(function|string)` - filter colors to update. Possible values:
--- - String representing target attributes. One of `'fg'`, `'bg'`, `'sp'`,
--- `'term'` (only terminal colors).
--- - Callable with signature as in |MiniColors-colorscheme:color_modify()|.
--- Default: `nil` to update all colors.
--- - <gamut_clip> `(string)` - gamut clipping method. One of `'chroma'`,
--- `'lightness'`, `'cusp'`. See |MiniColors-gamut-clip|. Default: `'chroma'`.
--- Colorscheme object is a central structure of this module. It contains all
--- data relevant to colors in fields and provides methods to modify it.
---
--- Create colorscheme object manually with |MiniColors.as_colorscheme()|: >lua
---
--- MiniColors.as_colorscheme({
--- name = 'my_cs',
--- groups = {
--- Normal = { fg = '#dddddd', bg = '#222222' },
--- SpellBad = { sp = '#dd2222', undercurl = true },
--- },
--- terminal = { [0] = '#222222', [1] = '#dd2222' },
--- })
--- <
--- Get any registered color scheme (including currently active) as colorscheme
--- object with |MiniColors.get_colorscheme()|: >lua
---
--- -- Get current color scheme
--- MiniColors.get_colorscheme()
---
--- -- Get registered color scheme by name
--- MiniColors.get_colorscheme('minischeme', { new_name = 'maxischeme' })
--- <
---@class Colorscheme
---
--- *MiniColors-colorscheme-fields*
---
---@field name string|nil Name of the color scheme (as in |g:colors_name|).
---
---@field groups table|nil Table with highlight groups data. Keys are group
--- names appropriate for `name` argument of |nvim_set_hl()|, values - tables
--- appropriate for its `val` argument. Note: gui colors are accepted only in
--- short form (`fg`, `bg`, `sp`).
---
---@field terminal table|nil Table with terminal colors data (|terminal-config|).
--- Keys are numbers from 0 to 15, values - strings representing color (hex
--- string or plain color name; see |nvim_get_color_by_name()|).
---
--- # Methods ~
--- *MiniColors-colorscheme-methods*
---
--- Notes about all methods:
--- - They never modify underlying colorscheme object instead returning deep
--- copy with modified fields.
--- - They accept `self` colorscheme object as first argument meaning they should be
--- called with `:` notation (like `cs:method()`).
---
--- Example calling methods: >lua
---
--- -- Get current color scheme, set hue of colors to 135, infer cterm
--- -- attributes and apply
--- local cs = MiniColors.get_colorscheme()
--- cs:chan_set('hue', 135):add_cterm_attributes():apply()
--- <
--- ## add_cterm_attributes() ~
--- *MiniColors-colorscheme:add_cterm_attributes()*
---
--- Infer |cterm-colors| based on present |gui-colors|. It updates `ctermbg`/`ctermfg`
--- based on `fg`/`bg` by approximating in perceptually uniform distance in Oklab
--- space (|MiniColors-color-spaces|).
---
--- ### Parameters ~
--- {opts} `(table|nil)` Options. Possible fields:
--- - <force> `(boolean)` - Whether to replace already present cterm attributes
--- with inferred ones. Default: `true`.
---
--- ## add_terminal_colors() ~
--- *MiniColors-colorscheme:add_terminal_colors()*
---
--- Infer terminal colors (|terminal-config|) based on colorscheme palette
--- (see |MiniColors-colorscheme:get_palette()|). It updates `terminal` field
--- based on color scheme's palette by picking the most appropriate entry to
--- represent terminal color. Colors from 0 to 7 are attempted to be black,
--- red, green, yellow, blue, magenta, cyan, white. Colors from 8 to 15 are
--- the same as from 0 to 7.
---
--- ### Parameters ~
--- {opts} `(table|nil)` Options. Possible fields:
--- - <force> `(boolean)` - Whether to replace already present terminal colors
--- with inferred ones. Default: `true`.
--- - <palette_args> `(table)` - |MiniColors-colorscheme:get_palette()| arguments.
---
--- ## add_transparency() ~
--- *MiniColors-colorscheme:add_transparency()*
---
--- Add transparency by removing background from a certain highlight groups.
--- Requires actual transparency from terminal emulator to see background image.
--- Has no effect on linked groups; use |MiniColors-colorscheme:resolve_links()|
--- explicitly before applying transparency.
---
--- ### Parameters ~
--- {opts} `(table|nil)` Options. Possible fields can be used to configure which
--- sets of highlight groups to update:
--- - <general> `(boolean)` - general groups (like `Normal`). Default: `true`.
--- - <float> `(boolean)` - built-in groups for floating windows. Default: `false`.
--- - <statuscolumn> `(boolean)` - groups related to 'statuscolumn' (signcolumn,
--- numbercolumn, foldcolumn, `DiagnosticSignXxx`, and `XxxMsg` groups). Also
--- updates groups for all currently defined signs. Default: `false`.
--- - <statusline> `(boolean)` - built-in groups for 'statusline'. Default: `false`.
--- - <tabline> `(boolean)` - built-in groups for 'tabline'. Default: `false`.
--- - <winbar> `(boolean)` - built-in groups for 'winbar'. Default: `false`.
---
--- ## apply() ~
--- *MiniColors-colorscheme:apply()*
---
--- Apply colorscheme:
--- - Set |g:colors_name| to a `name` field.
--- - Apply highlight groups in a `groups` field.
--- - Set terminal colors from a `terminal` field.
---
--- ### Parameters ~
--- {opts} `(table|nil)` Options. Possible fields:
--- - <clear> `(boolean)` - whether to execute |:hi-clear| first. Default: `true`.
---
--- ## chan_add() ~
--- *MiniColors-colorscheme:chan_add()*
---
--- Add value to a channel (see |MiniColors-channels|).
---
--- ### Parameters ~
--- {channel} __colors_channel
--- {value} `(number)` Number to add (can be negative).
--- {opts} __colors_chan_opts
---
---
--- ## chan_invert() ~
--- *MiniColors-colorscheme:chan_invert()*
---
--- Invert value in a channel (see |MiniColors-channels|).
---
--- Notes:
--- - Most Oklab/Oklch inversions are not exactly invertible: applying it twice
--- might lead to slightly different colors depending on gamut clip method
--- (|MiniColors-gamut-clip|) like smaller chroma with default `'chroma'` method.
---
--- ### Parameters ~
--- {channel} __colors_channel
--- {opts} __colors_chan_opts
---
--- ## chan_modify() ~
--- *MiniColors-colorscheme:chan_modify()*
---
--- Modify channel with a callable.
---
--- ### Parameters ~
--- {channel} __colors_channel
--- {f} `(function)` - callable which defines modification. Should take current
--- value of a channel and return a new one.
--- {opts} __colors_chan_opts
---
--- ## chan_multiply() ~
--- *MiniColors-colorscheme:chan_multiply()*
---
--- Multiply value of a channel (see |MiniColors-channels|).
---
--- ### Parameters ~
--- {channel} __colors_channel
--- {coef} `(number)` Number to multiply with (can be negative).
--- {opts} __colors_chan_opts
---
--- ## chan_repel() ~
--- *MiniColors-colorscheme:chan_repel()*
---
--- Repel from certain sources.
---
--- Given an array of repel centers (`sources`) and repel degree (`coef`) add to
--- current channel value some amount ("nudge") with the following properties:
--- - Nudges from several sources are added together.
--- - Nudge is directly proportional to `coef`: bigger `coef` means bigger nudge.
--- - Nudge is inversely proportional to the distance between current value and
--- source: for positive `coef` bigger distance means smaller nudge, i.e.
--- repel effect weakens with distance.
--- - With positive `coef` nudges close to source are computed in a way to remove
--- whole `[source - coef; source + coef]` range.
--- - Negative `coef` results into attraction to source. Nudges in
--- `[source - coef; source + coef]` range are computed to completely collapse it
--- into `source`.
---
--- Examples: >lua
---
--- -- Repel hue from red color removing hue in range from 20 to 40
--- chan_repel('hue', 30, 10)
---
--- -- Attract hue to red color collapsing [20; 40] range into 30.
--- chan_repel('hue', 30, -10)
--- <
--- ### Parameters ~
--- {channel} __colors_channel
--- {sources} `(table|number)` Single or multiple source from which to repel.
--- {coef} `(number)` Repel degree (can be negative to attract).
--- {opts} __colors_chan_opts
---
--- ## chan_set() ~
--- *MiniColors-colorscheme:chan_set()*
---
--- Set channel to certain value(s). This can be used to ensure that channel has
--- value(s) only within supplied set. If more than one is supplied, closest
--- element to current value is used.
---
--- ### Parameters ~
--- {channel} __colors_channel
--- {values} `(table|number)` Single or multiple values to set.
--- {opts} __colors_chan_opts
---
--- ## color_modify() ~
--- *MiniColors-colorscheme:color_modify()*
---
--- Modify all colors with a callable. It should return new color value (hex
--- string or `nil` to remove attribute) base on the following input:
--- - Current color as hex string.
--- - Data about the color: a table with fields:
--- - <attr> - one of `'fg'`, `'bg'`, `'sp'`, and `'term'` for terminal color.
--- - <name> - name of color source. Either a name of highlight group or
--- string of the form `terminal_color_x` for terminal color (as in
--- |terminal-config|).
---
--- Example: >lua
---
--- -- Set to '#dd2222' all foreground colors for groups starting with "N"
--- color_modify(function(hex, data)
--- if data.attr == 'fg' and data.name:find('^N') then
--- return '#dd2222'
--- end
--- return hex
--- end)
--- <
--- ### Parameters ~
--- {f} `(function)` Callable returning new color value.
---
--- ## compress() ~
--- *MiniColors-colorscheme:compress()*
---
--- Remove redundant highlight groups. These are one of the two kinds:
--- - Having values identical to ones after |:hi-clear| (meaning they usually
--- don't add new information).
--- - Coming from a curated list of plugins with highlight groups usually not
--- worth keeping around. Current list of such plugins:
--- - [nvim-tree/nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons)
--- - [norcalli/nvim-colorizer.lua](https://github.com/norcalli/nvim-colorizer.lua)
---
--- This method is useful to reduce size of color scheme before writing into
--- the file with |MiniColors-colorscheme:write()|.
---
--- ### Parameters ~
--- {opts} `(table|nil)` Options. Possible fields:
--- - <plugins> `(boolean)` - whether to remove highlight groups from a curated
--- list of plugins. Default: `true`.
---
--- ## get_palette() ~
--- *MiniColors-colorscheme:get_palette()*
---
--- Get commonly used colors. This basically counts number of all color
--- occurrences and filter out rare ones.
---
--- It is usually a good idea to apply both |MiniColors-colorscheme:compress()|
--- and |MiniColors-colorscheme:resolve_links()| before applying this.
---
--- ### Parameters ~
--- {opts} `(table|nil)` Options. Possible fields:
--- - <threshold> `(number)` - relative threshold for groups to keep. A group
--- is not included in output if it has less than this many occurrences
--- relative to a total number of colors. Default: 0.01.
---
--- ## resolve_links() ~
--- *MiniColors-colorscheme:resolve_links()*
---
--- Resolve links (|:highlight-link|). This makes all highlight groups with `link`
--- attribute have data from a linked one.
---
--- Notes:
--- - Resolves nested links.
--- - If some group is linked to a group missing in current colorscheme object,
--- it is not resolved.
---
--- ## simulate_cvd() ~
--- *MiniColors-colorscheme:simulate_cvd()*
---
--- Simulate color vision deficiency (CVD, color blindness). This is basically
--- a wrapper using |MiniColors.simulate_cvd()| as a part of
--- call to |MiniColors-colorscheme:color_modify()| method.
---
--- ### Parameters ~
--- {cvd_type} `(string)` One of `'protan'`, `'deutan'`, `'tritan'`, `'mono'`.
--- {severity} `(number|nil)` Severity of CVD, number between 0 and 1. Default: 1.
---
--- ## write() ~
--- *MiniColors-colorscheme:write()*
---
--- Write color scheme to a file. It will be a Lua script readily usable as
--- a regular color scheme. Useful to both save results of color scheme tweaking
--- and making local snapshot of some other color scheme.
---
--- Sourcing this file on startup usually leads to a better performance that
--- sourcing initial color scheme, as it is essentially a conditioned
--- |:hi-clear| call followed by a series of |nvim_set_hl()| calls.
---
--- Default writing location is a "colors" directory of your Neovim config
--- directory (see |base-directories|). After writing, it should be available
--- for sourcing with |:colorscheme| or |:Colorscheme|.
---
--- Name of the file by default is taken from `name` field (`'mini_colors'` is
--- used if it is `nil`). If color scheme with this name already exists, it
--- appends prefix based on current time to make it unique.
---
--- Notes:
--- - If colors were updated, it is usually a good idea to infer cterm attributes
--- with |MiniColors-colorscheme:add_cterm_attributes()| prior to writing.
---
--- ### Parameters ~
--- {opts} `(table|nil)` Options. Possible fields:
--- - <compress> `(boolean)` - whether to call |MiniColors-colorscheme:compress()|
--- prior to writing. Default: `true`.
--- - <name> `(string|nil)` - basename of written file. Default: `nil` to infer
--- from `name` field.
--- - <directory> `(string)` - directory to where file should be saved.
--- Default: "colors" subdirectory of Neovim home config (`stdpath("config")`).
---@tag MiniColors-colorscheme
---@diagnostic disable:undefined-field
---@diagnostic disable:discard-returns
---@diagnostic disable:unused-local
-- Module definition ==========================================================
local MiniColors = {}
local H = {}
--- Module setup
---
--- # :Colorscheme ~
--- *:Colorscheme*
---
--- Calling this function creates a `:Colorscheme` user command. It takes one or
--- more registered color scheme names and performs animated transition between
--- them (starting from currently active color scheme).
--- It uses |MiniColors.animate()| with default options.
---
---@param config table|nil Module config table. See |MiniColors.config|.
---
---@usage >lua
--- require('mini.colors').setup() -- use default config
--- -- OR
--- require('mini.colors').setup({}) -- replace {} with your config table
--- <
MiniColors.setup = function(config)
-- Export module
_G.MiniColors = MiniColors
-- Setup config
config = H.setup_config(config)
-- Apply config
H.apply_config(config)
-- Create user commands
H.create_user_commands()
end
--- Defaults ~
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
MiniColors.config = {}
--minidoc_afterlines_end
--- Create colorscheme object
---
---@param x table Table to be transformed into |MiniColors-colorscheme| object.
---
---@return table Copy of `x` transformed into a colorscheme object.
MiniColors.as_colorscheme = function(x)
-- Validate input
if not H.is_table(x) then H.error('Input of `as_colorscheme()` should be table.') end
if x.groups ~= nil then
if not H.is_table(x.groups) then H.error('Field `groups` of colorscheme should be table or nil.') end
if not H.all(x.groups, H.is_table) then H.error('All elements of `groups` colorscheme field should be tables.') end
end
if x.terminal ~= nil then
if not H.is_table(x.terminal) then H.error('Field `terminal` of colorscheme should be table or nil.') end
if not H.all(x.terminal, H.is_string) then
H.error('All elements of `terminal` colorscheme field should be strings.')
end
end
-- Create a proper copy
local res = vim.deepcopy(x)
-- - Ensure that tables for highlight groups are independent (can not be the
-- case if some point to literally the same table)
if H.is_table(res.groups) then
for key, val in pairs(res.groups) do
res.groups[key] = vim.deepcopy(val)
end
end
-- Fields
res.groups = res.groups or {}
res.name = res.name
res.terminal = res.terminal or {}
-- Methods
res.add_cterm_attributes = H.cs_add_cterm_attributes
res.add_terminal_colors = H.cs_add_terminal_colors
res.add_transparency = H.cs_add_transparency
res.apply = H.cs_apply
res.chan_add = H.cs_chan_add
res.chan_invert = H.cs_chan_invert
res.chan_modify = H.cs_chan_modify
res.chan_multiply = H.cs_chan_multiply
res.chan_repel = H.cs_chan_repel
res.chan_set = H.cs_chan_set
res.color_modify = H.cs_color_modify
res.compress = H.cs_compress
res.get_palette = H.cs_get_palette
res.resolve_links = H.cs_resolve_links
res.simulate_cvd = H.cs_simulate_cvd
res.write = H.cs_write
return res
end
--- Get colorscheme object from registered color scheme
---
---@param name string|nil Name of color scheme to use. If `nil` (default) creates
--- colorscheme object based on currently active data (|g:colors_name|,
--- highlight groups, terminal colors). If string, converts color scheme with
--- that name to a colorscheme object.
---@param opts table|nil Options. Possible fields:
--- - <new_name> `(string|nil)` - new name of colorscheme object.
---
---@return table Colorscheme object (|MiniColors-colorscheme|).
MiniColors.get_colorscheme = function(name, opts)
if not (name == nil or type(name) == 'string') then H.error('Argument `name` should be string or `nil`.') end
opts = vim.tbl_deep_extend('force', { new_name = nil }, opts or {})
-- Return current color scheme if no `name` is supplied
if name == nil then
return MiniColors.as_colorscheme({
name = opts.new_name or vim.g.colors_name,
groups = H.get_current_groups(),
terminal = H.get_current_terminal(),
})
end
-- Source supplied color scheme, collect it and return back
local current_cs = MiniColors.get_colorscheme()
local res, au_id
au_id = vim.api.nvim_create_autocmd('ColorScheme', {
callback = function()
res = MiniColors.get_colorscheme(nil, opts)
-- Apply right now to avoid flickering
current_cs:apply()
-- Explicitly delete autocommand to account for error in `:colorscheme`
vim.api.nvim_del_autocmd(au_id)
end,
})
local ok, _ = pcall(vim.cmd, 'colorscheme ' .. name)
if not ok then H.error(string.format('No color scheme named "%s".', name)) end
return res
end
--- Start interactive experiments
---
--- Create a special buffer in which user can write plain Lua code to tweak
--- color scheme and apply to get visual feedback.
---
--- # General principles ~
--- - Initial colorscheme object is fixed to interactive buffer on its creation.
---
--- - There are special buffer convenience mappings:
--- - Apply (source) current buffer content.
--- - Reset color scheme (make initial colorscheme the current one).
--- - Write to a file the result of applying current buffer content.
--- This sources current content and calls |MiniColors-colorscheme:write()|.
--- - Quit interactive buffer.
---
--- - User is expected to iteratively tweak color scheme by writing general Lua
--- code in interactive buffer and applying it using convenience mapping.
---
--- - Application of interactive buffer is essentially these steps:
--- - Expose `self` as initial colorscheme object on any application.
--- It is always the same for every application.
--- - Expose initial colorscheme methods as standalone functions. So instead
--- of writing `self = self:add_transparency()` user can only write
--- `add_transparency()`.
--- - Source buffer content as plain Lua code.
---
--- Example of interactive buffer content: >lua
---
--- chan_modify('hue', function() return math.random(0, 359) end)
--- simulate_cvd('protan')
--- add_cterm_attributes()
--- add_terminal_colors()
--- <
---@param opts table|nil Options. Possible fields:
--- - <colorscheme> `(table|nil)` - |MiniColors-colorscheme| object to be
--- used as initial colorscheme for executed code. By default uses current
--- color scheme.
--- - <mappings> `table` - buffer mappings for actions. Possible fields:
--- - <Apply> `(string)` - apply buffer code. Default: `'<M-a>'`.
--- - <Reset> `(string)` - apply initial color scheme as is. Default: `'<M-r>'`.
--- - <Quit> `(string)` - close interactive buffer. Default: `'<M-q>'`.
--- - <Write> `(string)` - write result of buffer code into a file.
--- Prompts for file name with |vim.ui.input()| and then
--- uses |MiniColors-colorscheme:write()| with other options being default.
--- Default: `'<M-w>'`.
MiniColors.interactive = function(opts)
opts = vim.tbl_deep_extend(
'force',
{ colorscheme = nil, mappings = { Apply = '<M-a>', Reset = '<M-r>', Quit = '<M-q>', Write = '<M-w>' } },
opts or {}
)
local maps = opts.mappings
-- Prepare
local init_cs = opts.colorscheme == nil and MiniColors.get_colorscheme()
or MiniColors.as_colorscheme(opts.colorscheme)
local buf_id = vim.api.nvim_create_buf(true, true)
H.set_buf_name(buf_id, 'interactive')
-- Write header lines
local header_lines = {
[[-- Experiment with color scheme using 'mini.colors']],
'--',
'-- Treat this as regular Lua file',
'-- Methods of initial color scheme can be called directly',
'-- See more in `:h MiniColors.interactive()`',
'--',
'-- Initial color scheme: ' .. (init_cs.name or '<unnamed>'),
'-- Buffer-local mappings (Normal mode):',
'-- Apply: ' .. maps.Apply,
'-- Reset: ' .. maps.Reset,
'-- Quit: ' .. maps.Quit,
'-- Write: ' .. maps.Write,
'--',
'-- Examples:',
'--',
'-- Invert dark/light color scheme to be light/dark',
"-- chan_invert('lightness', { gamut_clip = 'cusp' })",
'--',
'-- Make foreground text more saturated',
"-- chan_add('saturation', 20, { filter = 'fg' })",
'',
'',
}
vim.api.nvim_buf_set_lines(buf_id, 0, -1, true, header_lines)
-- Make local mappings
local m = function(action, rhs) vim.keymap.set('n', maps[action], rhs, { desc = action, buffer = buf_id }) end
m('Apply', function()
local new_cs = H.apply_interactive_buffer(buf_id, init_cs)
new_cs:apply()
end)
m('Reset', function() init_cs:apply() end)
m('Quit', function()
local ok, bufremove = pcall(require, 'mini.bufremove')
if ok then
bufremove.wipeout(buf_id, true)
else
vim.api.nvim_buf_delete(buf_id, { force = true })
end
end)
m('Write', function()
vim.ui.input(
{ prompt = [[Write to 'colors/' of your config under this name: ]], default = init_cs.name },
function(input)
if input == nil then return end
local new_cs = H.apply_interactive_buffer(buf_id, init_cs)
new_cs.name = input
new_cs:write({ name = input })
end
)
end)
-- Set local options
vim.bo[buf_id].filetype = 'lua'
-- Make current
vim.api.nvim_set_current_buf(buf_id)
vim.api.nvim_win_set_cursor(0, { vim.api.nvim_buf_line_count(buf_id), 0 })
end
--- Animate color scheme change
---
--- Start from currently active color scheme and loop through `cs_array`.
---
--- Powers |:Colorscheme| user command created in |MiniColors.setup()|.
---
---@param cs_array table Array of |MiniColors-colorscheme| objects.
---@param opts table|nil Options. Possible fields:
--- - <transition_steps> `(number)` - number of intermediate steps to show
--- during transition between two color schemes. Bigger values result in
--- smoother visual feedback but require more computational power.
--- Default: 25.
--- - <transition_duration> `(number)` - number of milliseconds to spend
--- showing transition. Default: 1000.
--- - <show_duration> `(number)` - number of milliseconds to show intermediate
--- color schemes (all but last in `cs_array`). Default: 1000.
MiniColors.animate = function(cs_array, opts)
if not (H.islist(cs_array) and H.all(cs_array, H.is_colorscheme)) then
H.error('Argument `cs_array` should be an array of color schemes.')
end
opts = vim.tbl_deep_extend(
'force',
{ transition_steps = 25, transition_duration = 1000, show_duration = 1000 },
opts or {}
)
if #cs_array == 0 then return end
-- Pre-compute common data
local cs_oklab = vim.tbl_map(function(cs) return H.cs_hex_to_oklab(vim.deepcopy(cs)) end, cs_array)
local cs_oklab_current = H.cs_hex_to_oklab(MiniColors.get_colorscheme())
-- Make "chain after action" which animates transitions one by one
local cs_id, after_action = 1, nil
after_action = function(data)
-- Ensure authentic color scheme is active
cs_array[cs_id]:apply()
-- Advance if possible
cs_id = cs_id + 1
if #cs_array < cs_id then return end
-- Wait before starting another animation
local callback = function() H.animate_single_transition(cs_oklab[cs_id - 1], cs_oklab[cs_id], after_action, opts) end
vim.defer_fn(callback, opts.show_duration)
end
H.animate_single_transition(cs_oklab_current, cs_oklab[1], after_action, opts)
end
--- Convert between color spaces
---
--- For a list of supported colors spaces see |MiniColors-color-spaces|.
---
---@param x table|string|number|nil Color to convert from. Its color space is
--- inferred automatically.
---@param to_space string Id of allowed color space.
---@param opts table|nil Options. Possible fields:
--- - <adjust_lightness> `(boolean)` - whether to adjust lightness value to have
--- a more uniform progression from 0 to 100. Set `false` for results more
--- compatible with some other Oklab/Oklch implementations (like in CSS).
--- Source: "Intermission - a new lightness estimate for Oklab" section of
--- https://bottosson.github.io/posts/colorpicker
--- Default: `true`.
--- - <gamut_clip> `(string)` - method for |MiniColors-gamut-clip|.
--- Default: `'chroma'`.
---
---@return table|string|number|nil Color in space `to_space` or `nil` if input is `nil`.
MiniColors.convert = function(x, to_space, opts)
if x == nil then return nil end
if not vim.tbl_contains(H.allowed_spaces, to_space) then
local spaces = table.concat(vim.tbl_map(vim.inspect, H.allowed_spaces), ', ')
H.error('Argument `to_space` should be one of ' .. spaces .. '.')
end
opts = vim.tbl_deep_extend('force', { adjust_lightness = true, gamut_clip = 'chroma' }, opts or {})
-- Set reference value here once to not have to pass it as argument to many
-- downstream places
H.adjust_lightness = opts.adjust_lightness