forked from nvim-mini/mini.nvim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclue.lua
More file actions
2074 lines (1855 loc) · 82 KB
/
clue.lua
File metadata and controls
2074 lines (1855 loc) · 82 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.clue* Show next key clues
---
--- MIT License Copyright (c) 2023 Evgeni Chasnovski
--- Features:
--- - Implement custom key query process to reach target key combination:
--- - Starts after customizable opt-in triggers (mode + keys).
---
--- - Each key press narrows down set of possible targets.
--- Pressing `<BS>` removes previous user entry.
--- Pressing `<Esc>` or `<C-c>` leads to an early stop.
--- Doesn't depend on 'timeoutlen' and has basic support for 'langmap'.
---
--- - Ends when there is at most one target left or user pressed `<CR>`.
--- Results into emulating pressing all query keys plus possible postkeys.
---
--- - Show window (after configurable delay) with clues. It lists available
--- next keys along with their descriptions (auto generated from descriptions
--- present keymaps and user-supplied clues; preferring the former).
---
--- - Configurable "postkeys" for key combinations - keys which will be emulated
--- after combination is reached during key query process.
---
--- - Provide customizable sets of clues for common built-in keys/concepts:
--- - `g` key.
--- - `z` key.
--- - Window commands.
--- - Built-in completion.
--- - Marks.
--- - Registers.
---
--- - Lua functions to disable/enable triggers globally or per buffer.
---
--- For more details see:
--- - |MiniClue-key-query-process|.
--- - |MiniClue-examples|.
--- - |MiniClue.config|.
--- - |MiniClue.gen_clues|.
---
--- Notes:
--- - There is no functionality to create mappings while defining clues.
--- This is done to clearly separate these two different actions.
--- The best suggested practice is to manually create mappings with
--- descriptions (`desc` field in options), as they will be automatically
--- used inside clue window.
---
--- - Triggers are implemented as special buffer-local mappings. This leads to
--- several caveats:
--- - They will override same regular buffer-local mappings and have
--- precedence over global one.
---
--- Example: having set `<C-w>` as Normal mode trigger means that
--- there should not be another `<C-w>` mapping.
---
--- - They need to be the latest created buffer-local mappings or they will
--- not function properly. Most common indicator of this is that some
--- mapping starts to work only after clue window is shown.
---
--- Example: `g` is set as Normal mode trigger, but `gcc` from |mini.comment|
--- doesn't work right away. This is probably because there are some
--- other buffer-local mappings starting with `g` which were created after
--- mapping for `g` trigger. Most common places for this are in LSP server's
--- `on_attach` or during tree-sitter start in buffer.
---
--- To check if trigger is the most recent buffer-local mapping, execute
--- `:<mode-char>map <trigger-keys>` (like `:nmap g` for previous example).
--- Mapping for trigger should be the first listed.
---
--- This module makes the best effort to work out of the box and cover
--- most common cases, but it is not foolproof. The solution here is to
--- ensure that triggers are created after making all buffer-local mappings:
--- run either |MiniClue.setup()| or |MiniClue.ensure_buf_triggers()|.
---
--- - Descriptions from existing mappings take precedence over user-supplied
--- clues. This is to ensure that information shown in clue window is as
--- relevant as possible. To add/customize description of an already existing
--- mapping, use |MiniClue.set_mapping_desc()|.
---
--- - Due to technical difficulties, there is no foolproof support for
--- Operator-pending mode triggers (like `a`/`i` from |mini.ai|):
--- - Doesn't work as part of a command in "temporary Normal mode" (like
--- after |i_CTRL-O|) due to implementation difficulties.
--- - Can have unexpected behavior with custom operators.
---
--- - Has (mostly solved) issues with macros:
--- - All triggers are disabled during macro recording due to technical
--- reasons.
--- - The `@` and `Q` keys are specially mapped inside |MiniClue.setup()|
--- (if the key is not already mapped) to temporarily disable triggers.
---
--- # Setup ~
---
--- This module needs a setup with `require('mini.clue').setup({})` (replace
--- `{}` with your `config` table). It will create global Lua table `MiniClue`
--- which you can use for scripting or manually (with `:lua MiniClue.*`).
---
--- Config table **needs to have triggers configured**, none is set up by default.
---
--- See |MiniClue.config| for available config settings.
---
--- You can override runtime config settings (like clues or window options)
--- locally to a buffer inside `vim.b.miniclue_config` which should have same
--- structure as `MiniClue.config`. See |mini.nvim-buffer-local-config| for
--- more details.
---
--- # Comparisons ~
---
--- - [folke/which-key.nvim](https://github.com/folke/which-key.nvim):
--- - Both have the same main goal: show available next keys along with
--- their customizable descriptions.
--- - Has different UI and content layout.
--- - Allows creating mappings inside its configuration, while this module
--- doesn't have this by design (to clearly separate two different tasks).
--- - Doesn't allow creating submodes, while this module does (via `postkeys`).
---
--- - [anuvyklack/hydra.nvim](https://github.com/anuvyklack/hydra.nvim):
--- - Both allow creating submodes: state which starts at certain key
--- combination; treats some keys differently; ends after `<Esc>`.
--- - Doesn't show information about available next keys (outside of
--- submodes), while that is this module's main goal.
---
--- # Highlight groups ~
---
--- - `MiniClueBorder` - window border.
--- - `MiniClueDescGroup` - group description in clue window.
--- - `MiniClueDescSingle` - single target description in clue window.
--- - `MiniClueNextKey` - next key label in clue window.
--- - `MiniClueNextKeyWithPostkeys` - next key label with postkeys in clue window.
--- - `MiniClueSeparator` - separator in clue window.
--- - `MiniClueTitle` - window title.
---
--- To change any highlight group, set it directly with |nvim_set_hl()|.
---
--- # Disabling ~
---
--- To disable creating triggers, set `vim.g.miniclue_disable` (globally) or
--- `vim.b.miniclue_disable` (for a buffer) to `true`. Considering high number
--- of different scenarios and customization intentions, writing exact rules
--- for disabling module's functionality is left to user. See
--- |mini.nvim-disabling-recipes| for common recipes.
---@tag MiniClue
--- # General info ~
---
--- This module implements custom key query process imitating a usual built-in
--- mechanism of user pressing keys in order to execute a mapping. General idea
--- is the same: narrow down key combinations until the target is reached.
---
--- Main goals of its existence are:
---
--- - Allow reaching certain mappings be independent of 'timeoutlen'. That is,
--- there is no fixed timeout after which currently typed keys are executed.
---
--- - Enable automated showing of next key clues after user-supplied delay
--- (also independent of 'timeoutlen').
---
--- - Allow emulating configurable key presses after certain key combination is
--- reached. This granular control allows creating so called "submodes".
--- See more at |MiniClue-examples-submodes|.
---
--- This process is primarily designed for nested `<Leader>` mappings in Normal
--- mode but works in all other main modes: Visual, Insert, Operator-pending
--- (with caveats; no foolproof guarantees), Command-line, Terminal.
---
--- # Lifecycle ~
---
--- - Key query process starts when user types a trigger: certain keys in certain
--- mode. Those keys are put into key query as a single user input. All possible
--- mode key combinations are filtered to ones starting with the trigger keys.
---
--- Note: trigger is implemented as a regular mapping, so if it has at least
--- two keys, they should be pressed within 'timeoutlen' milliseconds.
---
--- - Wait (indefinitely) for user to press a key. Advance depending on the key:
---
--- - Special key:
---
--- - If `<Esc>` or `<C-c>`, stop the process without any action.
---
--- - If `<CR>`, stop the process and execute current key query, meaning
--- emulate (with |nvim_feedkeys()|) user pressing those keys.
---
--- - If `<BS>`, remove previous user input from the query. If query becomes
--- empty, stop the process without any action.
---
--- - If a key for scrolling clue window (`scroll_down` / `scroll_up`
--- in `config.window`; `<C-d>` / `<C-u>` by default), scroll clue window
--- and wait for the next user key.
--- Note: if clue window is not shown, treated as a not special key.
---
--- - Not special key. Add key to the query while filtering all available
--- key combinations to start with the current key query. Advance:
---
--- - If there is a single available key combination matching current
--- key query, execute it.
---
--- - If there is no key combinations starting with the current query,
--- execute it. This, for instance, allows a seamless execution of
--- operators in presence of a longer key combinations. Example: with
--- `g` as trigger in Normal mode and available mappings `gc` / `gcc`
--- (like from |mini.comment|), this allows typing `gcip` to comment
--- current paragraph, although there are no key combinations
--- starting with `gci`.
---
--- - Otherwise wait for the new user key press.
---
--- # Clue window ~
---
--- After initiating key query process and after each key press, a timer is
--- started to show a clue window: floating window with information about
--- available next keys along with their descriptions. Note: if window is
--- already shown, its content is updated right away.
---
--- Clues can have these types:
---
--- - "Terminal next key": when pressed, will lead to query execution.
---
--- - "Terminal next key with postkeys": when pressed, will lead to query
--- execution plus some configured postkeys.
---
--- - "Group next key": when pressed, will narrow down available key combinations
--- and wait for another key press. Note: can have configured description
--- (inside `config.clues`) or it will be auto generated based on the number of
--- available key combinations.
---@tag MiniClue-key-query-process
--- # Full starter example ~
---
--- If not sure where to start, try this example with all provided clues from
--- this module plus all |<Leader>| mappings in Normal and Visual modes: >lua
---
--- local miniclue = require('mini.clue')
--- miniclue.setup({
--- triggers = {
--- -- Leader triggers
--- { mode = 'n', keys = '<Leader>' },
--- { mode = 'x', keys = '<Leader>' },
---
--- -- `[` and `]` keys
--- { mode = 'n', keys = '[' },
--- { mode = 'n', keys = ']' },
---
--- -- Built-in completion
--- { mode = 'i', keys = '<C-x>' },
---
--- -- `g` key
--- { mode = 'n', keys = 'g' },
--- { mode = 'x', keys = 'g' },
---
--- -- Marks
--- { mode = 'n', keys = "'" },
--- { mode = 'n', keys = '`' },
--- { mode = 'x', keys = "'" },
--- { mode = 'x', keys = '`' },
---
--- -- Registers
--- { mode = 'n', keys = '"' },
--- { mode = 'x', keys = '"' },
--- { mode = 'i', keys = '<C-r>' },
--- { mode = 'c', keys = '<C-r>' },
---
--- -- Window commands
--- { mode = 'n', keys = '<C-w>' },
---
--- -- `z` key
--- { mode = 'n', keys = 'z' },
--- { mode = 'x', keys = 'z' },
--- },
---
--- clues = {
--- -- Enhance this by adding descriptions for <Leader> mapping groups
--- miniclue.gen_clues.square_brackets(),
--- miniclue.gen_clues.builtin_completion(),
--- miniclue.gen_clues.g(),
--- miniclue.gen_clues.marks(),
--- miniclue.gen_clues.registers(),
--- miniclue.gen_clues.windows(),
--- miniclue.gen_clues.z(),
--- },
--- })
--- <
--- # Leader clues ~
---
--- Assume there are these |<Leader>| mappings set up: >lua
---
--- -- Set `<Leader>` before making any mappings and configuring 'mini.clue'
--- vim.g.mapleader = ' '
---
--- local nmap_leader = function(suffix, rhs, desc)
--- vim.keymap.set('n', '<Leader>' .. suffix, rhs, { desc = desc })
--- end
--- local xmap_leader = function(suffix, rhs, desc)
--- vim.keymap.set('x', '<Leader>' .. suffix, rhs, { desc = desc })
--- end
---
--- nmap_leader('bd', '<Cmd>lua MiniBufremove.delete()<CR>', 'Delete')
--- nmap_leader('bw', '<Cmd>lua MiniBufremove.wipeout()<CR>', 'Wipeout')
---
--- nmap_leader('lf', '<Cmd>lua vim.lsp.buf.format()<CR>', 'Format')
--- xmap_leader('lf', '<Cmd>lua vim.lsp.buf.format()<CR>', 'Format')
--- nmap_leader('lr', '<Cmd>lua vim.lsp.buf.rename()<CR>', 'Rename')
--- nmap_leader('lR', '<Cmd>lua vim.lsp.buf.references()<CR>', 'References')
--- <
--- The following setup will enable |<Leader>| as trigger in Normal and Visual
--- modes and add descriptions to mapping groups: >lua
---
--- require('mini.clue').setup({
--- -- Register `<Leader>` as trigger
--- triggers = {
--- { mode = 'n', keys = '<Leader>' },
--- { mode = 'x', keys = '<Leader>' },
--- },
---
--- -- Add descriptions for mapping groups
--- clues = {
--- { mode = 'n', keys = '<Leader>b', desc = '+Buffers' },
--- { mode = 'n', keys = '<Leader>l', desc = '+LSP' },
--- },
--- })
--- <
--- # Clues without mappings ~
---
--- Clues can be shown not only for actually present mappings. This is helpful for
--- showing clues for built-in key combinations. Here is an example of clues for
--- a subset of built-in completion (see |MiniClue.gen_clues.builtin_completion()|
--- to generate clues for all available completion sources): >lua
---
--- require('mini.clue').setup({
--- -- Make `<C-x>` a trigger. Otherwise, key query process won't start.
--- triggers = {
--- { mode = 'i', keys = '<C-x>' },
--- },
---
--- -- Register custom clues
--- clues = {
--- { mode = 'i', keys = '<C-x><C-f>', desc = 'File names' },
--- { mode = 'i', keys = '<C-x><C-l>', desc = 'Whole lines' },
--- { mode = 'i', keys = '<C-x><C-o>', desc = 'Omni completion' },
--- { mode = 'i', keys = '<C-x><C-s>', desc = 'Spelling suggestions' },
--- { mode = 'i', keys = '<C-x><C-u>', desc = "With 'completefunc'" },
--- }
--- })
--- <
--- # Triggers in special buffers ~
---
--- By default triggers are automatically created in listed ('buflisted') and some
--- special non-listed buffers. Use |MiniClue.ensure_buf_triggers()| to manually
--- enable in when you need them. For example: >vim
---
--- au FileType special_ft lua MiniClue.ensure_buf_triggers()
--- <
--- # Submodes ~
--- *MiniClue-examples-submodes*
---
--- Submode is a state initiated after pressing certain key combination ("prefix")
--- during which some keys are interpreted differently.
---
--- In this module submode can be implemented following these steps:
---
--- - Create mappings for each key inside submode. Left hand side of mappings
--- should consist from prefix followed by the key.
---
--- - Create clue for each key inside submode with `postkeys` value equal to
--- prefix. It would mean that after executing particular key combination from
--- this submode, pressing its prefix will be automatically emulated (leading
--- back to being inside submode).
---
--- - Register submode prefix (or some of its starting part) as trigger.
---
--- ## Submode examples ~
---
--- - Submode for moving with |mini.move|:
--- - Press `<Leader>m` to start submode.
--- - Press any of `h`/`j`/`k`/`l` to move selection/line.
--- - Press `<Esc>` to stop submode.
---
--- The code: >lua
---
--- require('mini.move').setup({
--- mappings = {
--- left = '<Leader>mh',
--- right = '<Leader>ml',
--- down = '<Leader>mj',
--- up = '<Leader>mk',
--- line_left = '<Leader>mh',
--- line_right = '<Leader>ml',
--- line_down = '<Leader>mj',
--- line_up = '<Leader>mk',
--- },
--- })
---
--- require('mini.clue').setup({
--- triggers = {
--- { mode = 'n', keys = '<Leader>m' },
--- { mode = 'x', keys = '<Leader>m' },
--- },
--- clues = {
--- { mode = 'n', keys = '<Leader>mh', postkeys = '<Leader>m' },
--- { mode = 'n', keys = '<Leader>mj', postkeys = '<Leader>m' },
--- { mode = 'n', keys = '<Leader>mk', postkeys = '<Leader>m' },
--- { mode = 'n', keys = '<Leader>ml', postkeys = '<Leader>m' },
--- { mode = 'x', keys = '<Leader>mh', postkeys = '<Leader>m' },
--- { mode = 'x', keys = '<Leader>mj', postkeys = '<Leader>m' },
--- { mode = 'x', keys = '<Leader>mk', postkeys = '<Leader>m' },
--- { mode = 'x', keys = '<Leader>ml', postkeys = '<Leader>m' },
--- },
--- })
--- <
--- - Submode for iterating buffers and windows with |mini.bracketed|:
--- - Press `[` or `]` to start key query process for certain direction.
--- - Press `b` / `w` to iterate buffers/windows until reach target one.
--- - Press `<Esc>` to stop submode.
---
--- The code: >lua
---
--- require('mini.bracketed').setup()
---
--- require('mini.clue').setup({
--- triggers = {
--- { mode = 'n', keys = ']' },
--- { mode = 'n', keys = '[' },
--- },
--- clues = {
--- { mode = 'n', keys = ']b', postkeys = ']' },
--- { mode = 'n', keys = ']w', postkeys = ']' },
---
--- { mode = 'n', keys = '[b', postkeys = '[' },
--- { mode = 'n', keys = '[w', postkeys = '[' },
--- },
--- })
--- <
--- - Submode for window commands using |MiniClue.gen_clues.windows()|:
--- - Press `<C-w>` to start key query process.
--- - Press keys which move / change focus / resize windows.
--- - Press `<Esc>` to stop submode.
---
--- The code: >lua
---
--- local miniclue = require('mini.clue')
--- miniclue.setup({
--- triggers = {
--- { mode = 'n', keys = '<C-w>' },
--- },
--- clues = {
--- miniclue.gen_clues.windows({
--- submode_move = true,
--- submode_navigate = true,
--- submode_resize = true,
--- })
--- },
--- })
--- <
--- # Window config ~
--- >lua
--- require('mini.clue').setup({
--- triggers = { { mode = 'n', keys = '<Leader>' } },
---
--- window = {
--- -- Show window immediately
--- delay = 0,
---
--- config = {
--- -- Compute window width automatically
--- width = 'auto',
---
--- -- Use double-line border
--- border = 'double',
--- },
--- },
--- })
--- <
---@tag MiniClue-examples
---@diagnostic disable:undefined-field
---@diagnostic disable:discard-returns
---@diagnostic disable:unused-local
---@diagnostic disable:cast-local-type
-- Module definition ==========================================================
local MiniClue = {}
local H = {}
--- Module setup
---
---@param config table|nil Module config table. See |MiniClue.config|.
---
---@usage >lua
--- require('mini.clue').setup({}) -- replace {} with your config table
--- -- needs `triggers` field present
--- <
MiniClue.setup = function(config)
-- Export module
_G.MiniClue = MiniClue
-- Setup config
config = H.setup_config(config)
-- Apply config
H.apply_config(config)
-- Define behavior
H.create_autocommands()
-- Create default highlighting
H.create_default_hl()
end
--stylua: ignore
--- Defaults ~
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
---@text # General info ~
---
--- - To use |<Leader>| as part of the config (either as trigger or inside clues),
--- set it prior to running |MiniClue.setup()|.
---
--- - See |MiniClue-examples| for examples.
---
--- # Clues ~
---
--- `config.clues` is an array with extra information about key combinations.
--- Each element can be one of:
--- - Clue table.
--- - Array (possibly nested) of clue tables.
--- - Callable (function) returning either of the previous two.
---
--- A clue table is a table with the following fields:
--- - <mode> `(string)` - single character describing **single** mode short-name of
--- key combination as in `nvim_set_keymap()` ('n', 'x', 'i', 'o', 'c', etc.).
--- - <keys> `(string)` - key combination for which clue will be shown.
--- "Human-readable" key names as in |key-notation| (like "<Leader>", "<Space>",
--- "<Tab>", etc.) are allowed.
--- - <desc> `(string|function|nil)` - optional key combination description which is
--- shown in clue window. If function, should return string description.
--- - <postkeys> `(string|nil)` - optional postkeys which will be executed
--- automatically after `keys`. Allows creation of submodes
--- (see |MiniClue-examples-submodes|).
---
--- Notes:
--- - Postkeys are literal simulation of keypresses with |nvim_feedkeys()|.
---
--- - Suggested approach to configuring clues is to create mappings with `desc`
--- field while supplying to `config.clues` only elements describing groups,
--- postkeys, and built-in mappings.
---
--- # Triggers ~
---
--- `config.triggers` is an array with information when |MiniClue-key-query-process|
--- should start. Each element is a trigger table with the fields <mode> and
--- <keys> which are treated the same as in clue table.
---
--- # Window ~
---
--- `config.window` defines behavior of clue window.
---
--- `config.window.delay` is a number of milliseconds after which clue window will
--- appear. Can be 0 to show immediately.
---
--- `config.window.config` is a table defining floating window characteristics
--- or a callable returning such table (will be called with identifier of
--- window's buffer already showing all clues). It should have the same
--- structure as in |nvim_open_win()| with the following enhancements:
--- - <width> field can be equal to `"auto"` leading to window width being
--- computed automatically based on its content. Default is fixed width of 30.
--- - <row> and <col> can be equal to `"auto"` in which case they will be
--- computed to "stick" to set anchor ("SE" by default; see |nvim_open_win()|).
--- This allows changing corner in which window is shown: >lua
---
--- -- Pick one anchor
--- local anchor = 'NW' -- top-left
--- local anchor = 'NE' -- top-right
--- local anchor = 'SW' -- bottom-left
--- local anchor = 'SE' -- bottom-right
---
--- require('mini.clue').setup({
--- window = {
--- config = { anchor = anchor, row = 'auto', col = 'auto' },
--- },
--- })
--- <
--- `config.window.scroll_down` / `config.window.scroll_up` are strings defining
--- keys which will scroll clue window down / up which is useful in case not
--- all clues fit in current window height. Set to empty string `''` to disable
--- either of them.
MiniClue.config = {
-- Array of extra clues to show
clues = {},
-- Array of opt-in triggers which start custom key query process.
-- **Needs to have something in order to show clues**.
triggers = {},
-- Clue window settings
window = {
-- Floating window config
config = {},
-- Delay before showing clue window
delay = 1000,
-- Keys to scroll inside the clue window
scroll_down = '<C-d>',
scroll_up = '<C-u>',
},
}
--minidoc_afterlines_end
--- Enable triggers in all listed buffers
MiniClue.enable_all_triggers = function()
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
-- Map only inside valid listed buffers
if vim.fn.buflisted(buf_id) == 1 then H.map_buf_triggers(buf_id) end
end
end
--- Enable triggers in buffer
---
---@param buf_id number|nil Buffer identifier. Default: current buffer.
MiniClue.enable_buf_triggers = function(buf_id)
buf_id = (buf_id == nil or buf_id == 0) and vim.api.nvim_get_current_buf() or buf_id
if not H.is_valid_buf(buf_id) then H.error('`buf_id` should be a valid buffer identifier.') end
H.map_buf_triggers(buf_id)
end
--- Disable triggers in all buffers
MiniClue.disable_all_triggers = function()
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
H.unmap_buf_triggers(buf_id)
end
end
--- Disable triggers in buffer
---
---@param buf_id number|nil Buffer identifier. Default: current buffer.
MiniClue.disable_buf_triggers = function(buf_id)
buf_id = (buf_id == nil or buf_id == 0) and vim.api.nvim_get_current_buf() or buf_id
if not H.is_valid_buf(buf_id) then H.error('`buf_id` should be a valid buffer identifier.') end
H.unmap_buf_triggers(buf_id)
end
--- Ensure all triggers are valid
MiniClue.ensure_all_triggers = function()
MiniClue.disable_all_triggers()
MiniClue.enable_all_triggers()
end
--- Ensure buffer triggers are valid
---
---@param buf_id number|nil Buffer identifier. Default: current buffer.
MiniClue.ensure_buf_triggers = function(buf_id)
MiniClue.disable_buf_triggers(buf_id)
MiniClue.enable_buf_triggers(buf_id)
end
--- Update description of an existing mapping
---
--- Notes:
--- - Uses buffer-local mapping in case there are both global and buffer-local
--- mappings with same mode and LHS. Similar to |maparg()|.
---
---@param mode string Mapping mode (as in `maparg()`).
---@param lhs string Mapping left hand side (as `name` in `maparg()`).
---@param desc string New description to set.
MiniClue.set_mapping_desc = function(mode, lhs, desc)
if type(mode) ~= 'string' then H.error('`mode` should be string.') end
if type(lhs) ~= 'string' then H.error('`lhs` should be string.') end
if type(desc) ~= 'string' then H.error('`desc` should be string.') end
local ok_get, map_data = pcall(vim.fn.maparg, lhs, mode, false, true)
if not ok_get or vim.tbl_count(map_data) == 0 then
local msg = string.format('No mapping found for mode %s and LHS %s.', vim.inspect(mode), vim.inspect(lhs))
H.error(msg)
end
map_data.desc = desc
local ok_set = pcall(vim.fn.mapset, mode, false, map_data)
if not ok_set then H.error(vim.inspect(desc) .. ' is not a valid description.') end
end
--- Generate pre-configured clues
---
--- This is a table with function elements. Call to actually get array of clues.
MiniClue.gen_clues = {}
--- Generate clues for built-in completion
---
--- Contains clues for the following triggers: >lua
---
--- { mode = 'i', keys = '<C-x>' }
--- <
---@return table Array of clues.
MiniClue.gen_clues.builtin_completion = function()
--stylua: ignore
return {
{ mode = 'i', keys = '<C-x><C-d>', desc = 'Defined identifiers' },
{ mode = 'i', keys = '<C-x><C-e>', desc = 'Scroll up' },
{ mode = 'i', keys = '<C-x><C-f>', desc = 'File names' },
{ mode = 'i', keys = '<C-x><C-i>', desc = 'Identifiers' },
{ mode = 'i', keys = '<C-x><C-k>', desc = 'Identifiers from dictionary' },
{ mode = 'i', keys = '<C-x><C-l>', desc = 'Whole lines' },
{ mode = 'i', keys = '<C-x><C-n>', desc = 'Next completion' },
{ mode = 'i', keys = '<C-x><C-o>', desc = 'Omni completion' },
{ mode = 'i', keys = '<C-x><C-p>', desc = 'Previous completion' },
{ mode = 'i', keys = '<C-x><C-s>', desc = 'Spelling suggestions' },
{ mode = 'i', keys = '<C-x><C-t>', desc = 'Identifiers from thesaurus' },
{ mode = 'i', keys = '<C-x><C-y>', desc = 'Scroll down' },
{ mode = 'i', keys = '<C-x><C-u>', desc = "With 'completefunc'" },
{ mode = 'i', keys = '<C-x><C-v>', desc = 'Like in command line' },
{ mode = 'i', keys = '<C-x><C-z>', desc = 'Stop completion' },
{ mode = 'i', keys = '<C-x><C-]>', desc = 'Tags' },
{ mode = 'i', keys = '<C-x>s', desc = 'Spelling suggestions' },
}
end
--- Generate clues for `g` key
---
--- Contains clues for the following triggers: >lua
---
--- { mode = 'n', keys = 'g' }
--- { mode = 'x', keys = 'g' }
--- <
---@return table Array of clues.
MiniClue.gen_clues.g = function()
local gr_clue = vim.fn.has('nvim-0.11') == 1 and { mode = 'n', keys = 'gr', desc = '+LSP' }
or { mode = 'n', keys = 'gr', desc = 'Virtual replace with character' }
local gr_clue_viz = vim.fn.has('nvim-0.11') == 1 and { mode = 'x', keys = 'gr', desc = '+LSP' } or {}
--stylua: ignore
return {
{ mode = 'n', keys = 'g0', desc = 'Go to leftmost visible column' },
{ mode = 'n', keys = 'g8', desc = 'Print hex value of char under cursor' },
{ mode = 'n', keys = 'ga', desc = 'Print ascii value' },
{ mode = 'n', keys = 'gD', desc = 'Go to definition in file' },
{ mode = 'n', keys = 'gd', desc = 'Go to definition in function' },
{ mode = 'n', keys = 'gE', desc = 'Go backwards to end of previous WORD' },
{ mode = 'n', keys = 'ge', desc = 'Go backwards to end of previous word' },
{ mode = 'n', keys = 'gF', desc = 'Edit file under cursor + jump line' },
{ mode = 'n', keys = 'gf', desc = 'Edit file under cursor' },
{ mode = 'n', keys = 'gg', desc = 'Go to line (def: first)' },
{ mode = 'n', keys = 'gH', desc = 'Start Select line mode' },
{ mode = 'n', keys = 'gh', desc = 'Start Select mode' },
{ mode = 'n', keys = 'gI', desc = 'Start Insert at column 1' },
{ mode = 'n', keys = 'gi', desc = 'Start Insert where it stopped' },
{ mode = 'n', keys = 'gJ', desc = 'Join lines without extra spaces' },
{ mode = 'n', keys = 'gj', desc = 'Go down by screen lines' },
{ mode = 'n', keys = 'gk', desc = 'Go up by screen lines' },
{ mode = 'n', keys = 'gM', desc = 'Go to middle of text line' },
{ mode = 'n', keys = 'gm', desc = 'Go to middle of screen line' },
{ mode = 'n', keys = 'gN', desc = 'Select previous search match' },
{ mode = 'n', keys = 'gn', desc = 'Select next search match' },
{ mode = 'n', keys = 'go', desc = 'Go to byte' },
{ mode = 'n', keys = 'gP', desc = 'Put text before cursor + stay after it' },
{ mode = 'n', keys = 'gp', desc = 'Put text after cursor + stay after it' },
{ mode = 'n', keys = 'gQ', desc = 'Switch to "Ex" mode' },
{ mode = 'n', keys = 'gq', desc = 'Format text (operator)' },
{ mode = 'n', keys = 'gR', desc = 'Enter Virtual Replace mode' },
gr_clue,
{ mode = 'n', keys = 'gs', desc = 'Sleep' },
{ mode = 'n', keys = 'gT', desc = 'Go to previous tabpage' },
{ mode = 'n', keys = 'gt', desc = 'Go to next tabpage' },
{ mode = 'n', keys = 'gU', desc = 'Make uppercase (operator)' },
{ mode = 'n', keys = 'gu', desc = 'Make lowercase (operator)' },
{ mode = 'n', keys = 'gV', desc = 'Avoid reselect' },
{ mode = 'n', keys = 'gv', desc = 'Reselect previous Visual area' },
{ mode = 'n', keys = 'gw', desc = 'Format text + keep cursor (operator)' },
{ mode = 'n', keys = 'gx', desc = 'Execute app for file under cursor' },
{ mode = 'n', keys = 'g<C-]>', desc = '`:tjump` to tag under cursor' },
{ mode = 'n', keys = 'g<C-a>', desc = 'Dump a memory profile' },
{ mode = 'n', keys = 'g<C-g>', desc = 'Show information about cursor' },
{ mode = 'n', keys = 'g<C-h>', desc = 'Start Select block mode' },
{ mode = 'n', keys = 'g<Tab>', desc = 'Go to last accessed tabpage' },
{ mode = 'n', keys = "g'", desc = "Jump to mark (don't affect jumplist)" },
{ mode = 'n', keys = 'g#', desc = 'Search backwards word under cursor' },
{ mode = 'n', keys = 'g$', desc = 'Go to rightmost visible column' },
{ mode = 'n', keys = 'g%', desc = 'Cycle through matching groups' },
{ mode = 'n', keys = 'g&', desc = 'Repeat last `:s` on all lines' },
{ mode = 'n', keys = 'g*', desc = 'Search word under cursor' },
{ mode = 'n', keys = 'g+', desc = 'Go to newer text state' },
{ mode = 'n', keys = 'g,', desc = 'Go to newer position in change list' },
{ mode = 'n', keys = 'g-', desc = 'Go to older text state' },
{ mode = 'n', keys = 'g;', desc = 'Go to older position in change list' },
{ mode = 'n', keys = 'g<', desc = 'Display previous command output' },
{ mode = 'n', keys = 'g?', desc = 'Rot13 encode (operator)' },
{ mode = 'n', keys = 'g@', desc = "Call 'operatorfunc' (operator)" },
{ mode = 'n', keys = 'g]', desc = '`:tselect` tag under cursor' },
{ mode = 'n', keys = 'g^', desc = 'Go to leftmost visible non-whitespace' },
{ mode = 'n', keys = 'g_', desc = 'Go to lower line' },
{ mode = 'n', keys = 'g`', desc = "Jump to mark (don't affect jumplist)" },
{ mode = 'n', keys = 'g~', desc = 'Swap case (operator)' },
{ mode = 'x', keys = 'gf', desc = 'Edit selected file' },
{ mode = 'x', keys = 'gJ', desc = 'Join selected lines without extra spaces' },
{ mode = 'x', keys = 'gq', desc = 'Format selection' },
gr_clue_viz,
{ mode = 'x', keys = 'gV', desc = 'Avoid reselect' },
{ mode = 'x', keys = 'gw', desc = 'Format selection + keep cursor' },
{ mode = 'x', keys = 'g<C-]>', desc = '`:tjump` to selected tag' },
{ mode = 'x', keys = 'g<C-a>', desc = 'Increment with compound' },
{ mode = 'x', keys = 'g<C-g>', desc = 'Show information about selection' },
{ mode = 'x', keys = 'g<C-x>', desc = 'Decrement with compound' },
{ mode = 'x', keys = 'g]', desc = '`:tselect` selected tag' },
{ mode = 'x', keys = 'g?', desc = 'Rot13 encode selection' },
}
end
--- Generate clues for `[` and `]` keys
---
--- Contains clues for the following triggers: >lua
---
--- { mode = 'n', keys = '[' }
--- { mode = 'n', keys = ']' }
--- <
---@return table Array of clues.
MiniClue.gen_clues.square_brackets = function()
--stylua: ignore
return {
{ mode = 'n', keys = '[<C-D>', desc = 'Go to first macro def with cursor word', },
{ mode = 'n', keys = '[<C-I>', desc = 'Go to first match with cursor word', },
{ mode = 'n', keys = '[%', desc = 'Go to previous unmatched group' },
{ mode = 'n', keys = '[#', desc = 'Go to previous unmatched #if/#else/#ifdef' },
{ mode = 'n', keys = "['", desc = 'Go to previous mark, first non-blank' },
{ mode = 'n', keys = '[`', desc = 'Go to previous mark' },
{ mode = 'n', keys = '[(', desc = "Go to previous unmatched '('" },
{ mode = 'n', keys = '[/', desc = 'Go to previous C comment start' },
{ mode = 'n', keys = '[*', desc = 'Go to previous C comment start' },
{ mode = 'n', keys = '[I', desc = 'Show lines with cursor word', },
{ mode = 'n', keys = '[D', desc = 'Show macro defs with cursor word' },
{ mode = 'n', keys = '[p', desc = 'Paste with current indent' },
{ mode = 'n', keys = '[P', desc = 'Paste with current indent' },
{ mode = 'n', keys = '[[', desc = 'Go to previous section' },
{ mode = 'n', keys = '[]', desc = 'Go to previous SECTION' },
{ mode = 'n', keys = '[c', desc = 'Go to previous change' },
{ mode = 'n', keys = '[d', desc = 'Show first macro def with cursor word' },
{ mode = 'n', keys = '[f', desc = 'Edit file under cursor' },
{ mode = 'n', keys = '[i', desc = 'Show first line with cursor word', },
{ mode = 'n', keys = '[m', desc = 'Go to previous method start' },
{ mode = 'n', keys = '[M', desc = 'Go to previous method end' },
{ mode = 'n', keys = '[s', desc = 'Go to previous misspelled word' },
{ mode = 'n', keys = '[z', desc = 'Go to current open fold start' },
{ mode = 'n', keys = '[{', desc = "Go to previous unmatched '{'" },
{ mode = 'n', keys = ']<C-D>', desc = 'Go to next macro def with cursor word', },
{ mode = 'n', keys = ']<C-I>', desc = 'Go to next match with cursor word', },
{ mode = 'n', keys = ']%', desc = 'Go to next unmatched group' },
{ mode = 'n', keys = ']#', desc = 'Go to next unmatched #if/#else/#ifdef' },
{ mode = 'n', keys = "]'", desc = "Go to next mark, first non-blank" },
{ mode = 'n', keys = ']`', desc = 'Go to next mark' },
{ mode = 'n', keys = '])', desc = "Go to next unmatched ')'" },
{ mode = 'n', keys = ']/', desc = 'Go to next C comment end' },
{ mode = 'n', keys = ']*', desc = 'Go to next C comment end' },
{ mode = 'n', keys = ']D', desc = 'Show below macro defs with cursor word' },
{ mode = 'n', keys = ']I', desc = 'Show below lines with cursor word', },
{ mode = 'n', keys = ']P', desc = 'Paste with current indent' },
{ mode = 'n', keys = '][', desc = 'Go to next SECTION' },
{ mode = 'n', keys = ']]', desc = 'Go to next section' },
{ mode = 'n', keys = ']c', desc = 'Go to next change' },
{ mode = 'n', keys = ']d', desc = 'Show next macro def with cursor word' },
{ mode = 'n', keys = ']f', desc = 'Edit file under cursor' },
{ mode = 'n', keys = ']i', desc = 'Show next line with cursor word', },
{ mode = 'n', keys = ']m', desc = 'Go to next method start' },
{ mode = 'n', keys = ']M', desc = 'Go to next method end' },
{ mode = 'n', keys = ']p', desc = 'Paste with current indent' },
{ mode = 'n', keys = ']s', desc = 'Go to next misspelled word' },
{ mode = 'n', keys = ']z', desc = 'Go to current open fold end' },
{ mode = 'n', keys = ']}', desc = "Go to next unmatched '}'" },
}
end
--- Generate clues for marks
---
--- Contains clues for the following triggers: >lua
---
--- { mode = 'n', keys = "'" }
--- { mode = 'n', keys = "g'" }
--- { mode = 'n', keys = '`' }
--- { mode = 'n', keys = 'g`' }
--- { mode = 'x', keys = "'" }
--- { mode = 'x', keys = "g'" }
--- { mode = 'x', keys = '`' }
--- { mode = 'x', keys = 'g`' }
--- <
--- Note: if you use "g" as trigger (like to enable |MiniClue.gen_clues.g()|),
--- don't add "g'" and "g`" as triggers: they already will be taken into account.
---
---@return table Array of clues.
---
---@seealso |mark-motions|
MiniClue.gen_clues.marks = function()
local describe_marks = function(mode, prefix)
local make_clue = function(register, desc) return { mode = mode, keys = prefix .. register, desc = desc } end
return {
make_clue('^', 'Latest insert position'),
make_clue('.', 'Latest change'),
make_clue('"', 'Latest exited position'),
make_clue("'", 'Line before jump'),
make_clue('`', 'Position before jump'),
make_clue('[', 'Start of latest changed or yanked text'),
make_clue(']', 'End of latest changed or yanked text'),
make_clue('(', 'Start of sentence'),
make_clue(')', 'End of sentence'),
make_clue('{', 'Start of paragraph'),
make_clue('}', 'End of paragraph'),
make_clue('<', 'Start of latest visual selection'),
make_clue('>', 'End of latest visual selection'),
}
end
--stylua: ignore
return {
-- Normal mode
describe_marks('n', "'"),
describe_marks('n', "g'"),
describe_marks('n', "`"),
describe_marks('n', "g`"),
-- Visual mode
describe_marks('x', "'"),
describe_marks('x', "g'"),
describe_marks('x', "`"),
describe_marks('x', "g`"),
}
end
--- Generate clues for registers
---
--- Contains clues for the following triggers: >lua
---
--- { mode = 'n', keys = '"' }
--- { mode = 'x', keys = '"' }
--- { mode = 'i', keys = '<C-r>' }
--- { mode = 'c', keys = '<C-r>' }
--- <
---@param opts table|nil Options. Possible keys:
--- - <show_contents> `(boolean)` - whether to show contents of all possible
--- registers. If `false`, only description of special registers is shown.
--- Default: `false`.
---
---@return table Array of clues.
---
---@seealso |registers|
MiniClue.gen_clues.registers = function(opts)
opts = vim.tbl_deep_extend('force', { show_contents = false }, opts or {})
local describe_registers
if opts.show_contents then
describe_registers = H.make_clues_with_register_contents
else
describe_registers = function(mode, prefix)
local make_clue = function(register, desc) return { mode = mode, keys = prefix .. register, desc = desc } end
return {
make_clue('0', 'Latest yank'),
make_clue('1', 'Latest big delete'),
make_clue('"', 'Default register'),
make_clue('#', 'Alternate buffer'),
make_clue('%', 'Name of the current file'),
make_clue('*', 'Selection clipboard'),
make_clue('+', 'System clipboard'),
make_clue('-', 'Latest small delete'),
make_clue('.', 'Latest inserted text'),
make_clue('/', 'Latest search pattern'),
make_clue(':', 'Latest executed command'),
make_clue('=', 'Result of expression'),
make_clue('_', 'Black hole'),
}
end
end
--stylua: ignore
return {
-- Normal mode
describe_registers('n', '"'),
-- Visual mode
describe_registers('x', '"'),
-- Insert mode
describe_registers('i', '<C-r>'),
{ mode = 'i', keys = '<C-r><C-r>', desc = '+Insert literally' },
describe_registers('i', '<C-r><C-r>'),
{ mode = 'i', keys = '<C-r><C-o>', desc = '+Insert literally + not auto-indent' },
describe_registers('i', '<C-r><C-o>'),
{ mode = 'i', keys = '<C-r><C-p>', desc = '+Insert + fix indent' },
describe_registers('i', '<C-r><C-p>'),
-- Command-line mode
describe_registers('c', '<C-r>'),
{ mode = 'c', keys = '<C-r><C-r>', desc = '+Insert literally' },
describe_registers('c', '<C-r><C-r>'),
{ mode = 'c', keys = '<C-r><C-o>', desc = '+Insert literally' },
describe_registers('c', '<C-r><C-o>'),
}
end
--- Generate clues for window commands
---
--- Contains clues for the following triggers: >lua