forked from nvim-mini/mini.nvim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinput.lua
More file actions
2183 lines (1945 loc) · 88.9 KB
/
Copy pathinput.lua
File metadata and controls
2183 lines (1945 loc) · 88.9 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.input* Get user input
---
--- MIT License Copyright (c) 2026 Evgeni Chasnovski
--- Features:
---
--- - Get user input with fully customizable key and view handling.
---
--- - Built-in configurable views as floating window, statusline/tabline/winbar,
--- virtual line/text.
---
--- - Implementation is non-blocking but waits to return the input. It also works
--- in any mode without requiring mode change. See |MiniInput-lifecycle|.
---
--- - |vim.ui.input()| implementation. To adjust, use |MiniInput.ui_input()| or
--- save-restore `vim.ui.input` manually after calling |MiniInput.setup()|.
---
--- Sources with more details:
--- - |MiniInput.get()|
--- - |MiniInput.default_key()|
--- - |MiniInput-state|
--- - |MiniInput-examples|
--- - |MiniInput-in-other-plugins| (for plugin authors)
---
--- # Setup ~
---
--- This module needs a setup with `require('mini.input').setup({})` (replace `{}`
--- with your `config` table). It will create global Lua table `MiniInput` which
--- you can use for scripting or manually (with `:lua MiniInput.*`).
---
--- See |MiniInput.config| for `config` structure and default values.
---
--- You can override runtime config settings locally to buffer inside
--- `vim.b.miniinput_config` which should have same structure as
--- `MiniInput.config`. See |mini.nvim-buffer-local-config| for more details.
---
--- # Comparisons ~
---
--- - [folke/snacks.nvim#input](https://github.com/folke/snacks.nvim):
--- - Both provide |vim.ui.input()| implementation.
--- - Has asynchronous implementation (i.e. does not wait for user to finish
--- input), while this module has synchronous non-blocking implementation.
--- - Uses floating window and forced Insert mode. This module allows more
--- view customizations and can be used in any mode without interruptions.
---
--- - |input()|:
--- - Both are synchronous.
--- - Both allow custom highlight and completion.
--- - This module also allows supplying input scope and visibility,
--- customizing keys and view.
---
--- # Highlight groups ~
--- *MiniInput-hl-groups*
---
--- - `MiniInputAdded` - added text during completion navigation.
--- - `MiniInputBorder` - border of a |MiniInput.gen_view.floatwin()| handler.
--- - `MiniInputCaret` - caret symbol shown in a prompt area.
--- - `MiniInputHide` - input is hidden, usually used instead of `MiniInputPrompt`.
--- - `MiniInputHint` - hints shown during completion navigation.
--- - `MiniInputNormal` - basic foreground/background.
--- - `MiniInputPrompt` - input prompt (intention of the input).
--- - `MiniInputSpecial` - special keys (like literal `\t`, `\n`, etc.) in input.
---
--- # Using in other plugins ~
--- *MiniInput-in-other-plugins*
---
--- - Prefer using |vim.ui.input()| for more user coverage. Use |MiniInput.get()|
--- only when getting input synchronously is absolutely necessary.
---
--- - Perform a `_G.MiniInput ~= nil` check before using any feature. This ensures
--- that user explicitly set up the module.
---@tag MiniInput
--- Information about the state of the input. It is passed as a handler argument.
--- Use |MiniInput.get_state()| to get information about the current state (if any).
--- A table with the following fields:
---
--- - <caret> `(number)` - character (not byte) index at which to modify input.
--- Should be from 1 (prepend input) to "input width plus 1" (append input).
---
--- - <complete> `(table|nil)` - information about active completion navigation.
--- If present, it means that completion navigation is in action.
--- Its fields describe the state of navigation:
--- - <base> `(string)` - reference text to the left of caret at the start
--- of completion. Used to compute candidates. Can be empty string.
--- - <id> `(number)` - identifier of current completion item. Can be zero to
--- mean that the base is shown. If not zero, must mean that a `items[id]`
--- candidate is now shown to the left of caret as the part of the input.
--- - <items> `(table)` - string array of completion candidates. May be empty.
--- Are intended to fully replace <base>, so should contain it in any sense.
--- - <method> `(string)` - completion method. Like `"default"`, `"history"`, etc.
--- Can be `""` (empty string) to mean "default method of complete handler".
---
--- - <data> `(table)` - any information to be reused within the same input session.
--- Note: handlers should not change fields that they don't "own".
---
--- - <errmsg> `(errmsg)` - first error message caught during input process.
---
--- - <highlight> `(table|nil)` - information about current input highlighting.
--- If absent, the whole input is highlighted using `MiniInputNormal` group.
--- Should be an array of highlight ranges. They might be not ordered, overlap,
--- go outside of input width. It is up to the view handler to decide how to
--- interpret them. See |MiniInput.state_to_chunks()| for a helper.
--- Fields of a single highlight range:
--- - <from> `(number)` - character index (one-indexed) of range start.
--- - <to> `(number)` - character index (one-indexed) of range end (inclusive).
--- Should not be smaller than <from>. Can be |math.huge|.
--- - <hl> `(string)` - highlight group to use for highlighting.
---
--- - <input> `(string)` - current user input. Returned by |MiniInput.get()|.
---
--- - <opts> `(table)` - input options, same as in |MiniInput.get()|.
---
--- - <prompt> `(string)` - intention of the input. May be empty.
---
--- - <status> `(string)` - one of `"start"`, `"progress"`, `"accept"`, `"cancel"`.
---@tag MiniInput-state
--- # General ~
---
--- ## Initial input ~
---
--- Use `opts.init_keys` to imitate the initial state of the input: >lua
---
--- MiniInput.get({ init_keys = { 'Default' } })
--- <
--- ## Custom mappings ~
---
--- Override `handlers.key` in |MiniInput.config|: >lua
---
--- local key_handler = function(state, key)
--- -- <C-a> - move caret to start of line
--- if key == '\1' then
--- state.caret = 1
--- -- <S-BS> - clear all input
--- elseif key == vim.keycode('<S-BS>') then
--- state.input, state.caret = '', 1
--- else
--- -- IMPORTANT: Fall back to processing as usual
--- return MiniInput.default_key(state, key)
--- end
--- end
---
--- require('mini.input').setup({ handlers = { key = key_handler } })
--- <
--- ## Basic command line ~
---
--- An alternative |Command-line| with highlighting and completion: >lua
---
--- -- Construct reusable `MiniInput.get()` options
--- local cmdline_opts = { prompt = 'Command', scope = 'editor' }
--- -- - Highlight using bundled Vim tree-sitter parser and default handler
--- local highlight_vim = MiniInput.gen_highlight.treesitter('vim')
--- local highlight_cmdline = function(state)
--- state = highlight_vim(state) or state
--- return MiniInput.default_highlight(state) or state
--- end
--- cmdline_opts.handlers = { highlight = highlight_cmdline }
--- -- - Complete as if it is Command line input
--- cmdline_opts.completion = 'cmdline'
---
--- -- Create a mapping for `:`
--- local input_cmdline = function()
--- local cmd = MiniInput.get(cmdline_opts)
--- if cmd ~= nil then vim.cmd(cmd) end
--- end
--- vim.keymap.set('n', ':', input_cmdline)
--- <
--- # Handlers ~
---
--- ## Key ~
---
--- Perform custom actions based on arbitrary conditions: >lua
---
--- local key_handler = function(state, key)
--- -- Adjust prompt
--- state.opts.prompt = state.opts.prompt:gsub('[?:]%s*$', '')
---
--- -- Adjust scope
--- if state.opts.prompt == 'Editor action' then
--- state.opts.scope = 'editor'
--- end
---
--- -- Hide from view and history
--- if state.opts.prompt:find('[Pp]assword') ~= nil then
--- state.opts.hide = true
--- end
---
--- -- IMPORTANT: Process as usual
--- state = MiniInput.default_key(state, key) or state
---
--- -- Auto fill and accept
--- if state.input == 'AF' then
--- state.input, state.status = 'Autofilled input', 'accept'
--- end
--- end
--- require('mini.input').setup({ handlers = { key = key_handler } })
--- <
--- ## View ~
---
--- Show no view: >lua
---
--- require('mini.input').setup({ handlers = { view = function() end } })
--- <
--- Compute initial style depending on scope: >lua
---
--- local input = require('mini.input')
--- local view_virtline = input.gen_view.virtual({ style = 'above' })
--- local view_tabline = input.gen_view.uiline({ style = 'tabline' })
--- local view_winbar = input.gen_view.uiline({ style = 'winbar' })
--- local view_handler = function(state)
--- -- NOTE: does not support interactive scope change
--- local scope, view = state.opts.scope, view_tabline
--- if scope == 'buffer' or scope == 'window' then view = view_winbar end
--- if scope == 'cursor' or scope == 'line' then view = view_virtline end
--- return view(state)
--- end
---
--- input.setup({ handlers = { view = view_handler } })
--- <
--- Change symbols for caret and hidden input: see |MiniInput.gen_view|.
---@tag MiniInput-examples
---@alias __input_to_chunks - <to_chunks> `(function)` - a function that takes |MiniInput-state| and
--- `max_width` arguments and returns an array of `{ text, hl }` chunks that fit
--- into `max_width` display width. See |MiniInput.state_to_chunks()|.
---@diagnostic disable:undefined-field
---@diagnostic disable:discard-returns
---@diagnostic disable:unused-local
-- Module definition ==========================================================
local MiniInput = {}
local H = {}
--- Module setup
---
---@param config table|nil Module config table. See |MiniInput.config|.
---
---@usage >lua
--- require('mini.input').setup() -- use default config
--- -- OR
--- require('mini.input').setup({}) -- replace {} with your config table
--- <
MiniInput.setup = function(config)
-- TODO: Remove after Neovim=0.9 support is dropped
if vim.fn.has('nvim-0.10') == 0 then
vim.notify(
'(mini.input) Neovim<0.10 is soft deprecated (module works but is not supported).'
.. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
.. ' Please update your Neovim version.'
)
end
-- Export module
_G.MiniInput = MiniInput
-- 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()
-- Set custom implementation
vim.ui.input = MiniInput.ui_input
-- Adjust terminal emulator's pasting with active input
H.adjust_vim_paste()
end
--- Defaults ~
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
---@text # Handlers ~
--- *MiniInput.config.handlers*
---
--- `config.handlers` defines functions that are applied during |MiniInput-lifecycle|.
--- Every handler takes |MiniInput-state| as the first argument and is expected to
--- either modify it in place or return a new state table.
---
--- Use |MiniInput.apply_handler()| to apply a handler for a given |MiniInput-state|.
--- They can be set up as part of the config (will be used as default) or passed
--- directly as a part of |MiniInput.get()| call.
---
--- ## Complete ~
---
--- `handlers.complete` is a handler intended to compute completion suggestions.
--- Takes |MiniInput-state| and `method` as arguments and is expected to modify
--- <complete> field. Default: |MiniInput.default_complete()|.
---
--- Only `complete.base` and `complete.items` are expected to be set by a complete
--- handler. Setting and modifying other fields (`complete.id` and `complete.method`)
--- is done in other handlers and as part of |MiniInput.apply_handler()|. Actually
--- showing completion information is up to the view handler.
---
--- This handler is usually applied manually inside a key handler via using
--- `MiniInput.apply_handler(state, 'complete', method)`. Like, for example,
--- in |MiniInput.default_key()| after <Tab> or <Up>.
---
--- Here is an example of a simple demo complete handler: >lua
---
--- local complete_handler = function(state, method)
--- if method == '' or method == 'xy' then
--- local text = vim.fn.strcharpart(state.input, 0, state.caret - 1)
--- local base = text:match('%S*$')
--- state.complete = { base = base, items = { base .. 'x', base .. 'y' } }
--- return
--- end
--- return MiniInput.default_complete(state, method)
--- end
---
--- require('mini.input').setup({ handlers = { complete = complete_handler } })
--- <
--- ## Key ~
---
--- `handlers.key` is a handler intended to process every user key press.
--- Takes |MiniInput-state| and `key` as arguments. Argument `key` represents a key
--- that needs to be processed: a string if from the user input or `nil` if input
--- needs to be set up, refreshed, or torn down. Default: |MiniInput.default_key()|.
---
--- A string `key` can be two kinds:
--- - Forwarded from |getcharstr()| verbatim as a result of interactive key press.
--- Meaning it will be in escaped form and not as a |key-notation|: i.e. `"\r"`
--- and not `"<CR>"`. It also means that all kinds of combos (`<M-...>`, `<C-S-...>`),
--- mouse clicks, and wheel scrolls are also forwarded to key handler.
--- - Any string as part of `opts.init_keys` in |MiniInput.get()|. If a string
--- doesn't look like it came from |getcharstr()|, it is usually a good idea
--- to insert this string at caret as is.
---
--- The suggested overall approach for custom key handler is "if `key` is special -
--- act on it, if can be used in the input - insert at caret, ignore otherwise".
--- It is also important to never change the current mode (as in |vim-modes|)
--- for |MiniInput.get()| to work as expected.
---
--- Here is an example of a basic custom key handler: >lua
---
--- local custom_actions = {
--- [vim.keycode('<Left>')] = function(state)
--- state.caret = math.max(state.caret - 1, 1)
--- end,
--- [vim.keycode('<Right>')] = function(state)
--- local input_width = vim.fn.strchars(state.input)
--- state.caret = math.min(state.caret + 1, input_width + 1)
--- end,
--- [vim.keycode('<CR>')] = function(state) state.status = 'accept' end,
--- [vim.keycode('<Esc>')] = function(state) state.status = 'cancel' end,
--- }
---
--- local key_handler = function(state, key)
--- -- No need for special setup or teardown
--- if key == nil then return end
---
--- -- If key is special - act on it
--- if custom_actions[key] then return custom_actions[key](state) end
---
--- -- If key is not printable - do nothing
--- if vim.fn.match(key, '^[[:print:]]\\+$') < 0 then return end
---
--- -- Insert at caret
--- local caret, input = state.caret, state.input
--- local before_caret = vim.fn.strcharpart(input, 0, caret - 1)
--- local after_caret = vim.fn.strcharpart(input, caret - 1)
--- state.input = before_caret .. key .. after_caret
--- state.caret = caret + vim.fn.strchars(key)
---
--- -- No need to return anything as `state` is modified in place
--- end
---
--- require('mini.input').setup({ handlers = { key = key_handler } })
--- <
--- ## Highlight ~
---
--- `handlers.highlight` is a handler intended to compute and set highlight info
--- about the current input. Takes |MiniInput-state| as the only argument and is
--- expected to modify <highlight> field. Default: |MiniInput.default_highlight()|.
---
--- See |MiniInput.gen_highlight| for built-in highlight handler generators.
---
--- It is usually a good idea to append to a <highlight> if it already exists.
--- This makes it work more robustly when combining highlights.
---
--- Here is a basic example that highlights all letters `a`: >lua
---
--- local hl_handler = function(state)
--- local highlight = {}
--- for col in string.gmatch(state.input, '()a') do
--- -- NOTE: range should use character (not byte) indexes
--- local char_col = vim.fn.charidx(state.input, col)
--- local range = { from = char_col, to = char_col, hl = 'Special' }
--- table.insert(highlight, range)
--- end
---
--- state.highlight = vim.list_extend(state.highlight or {}, highlight)
---
--- -- Possibly also apply default handler afterwards
--- return MiniInput.default_highlight(state)
--- end
---
--- require('mini.input').setup({ handlers = { highlight = hl_handler } })
--- <
--- ## View ~
---
--- `handlers.view` is a handler intended to show the input state on screen.
--- Takes |MiniInput-state| as the only argument. Default: |MiniInput.default_view()|.
---
--- See |MiniInput.gen_view| for built-in view handler generators.
---
--- Example of view that uses |nvim_echo()| to show the input: >lua
---
--- local view_handler = function(state)
--- -- Process start and end of the input lifecycle
--- local is_start = state.status == 'start'
--- local is_end = state.status == 'accept' or state.status == 'cancel'
--- if is_start or is_end then vim.cmd('mode') end
--- if is_end then return end
---
--- -- Compute text-hl chunks that fit and show them
--- local chunks = MiniInput.state_to_chunks(state, vim.v.echospace)
--- vim.api.nvim_echo(chunks, false, {})
--- end
---
--- require('mini.input').setup({ handlers = { view = view_handler } })
--- <
--- # Scope ~
---
--- `config.scope` is a string that defines an input scope. It is meant as an extra
--- information for handlers to tweak their behavior (`view` style, etc.). Possible
--- values: `"cursor"`, `"line"`, `"buffer"`, `"window"`, `"tabpage"`, `"editor"`, `"project"`.
MiniInput.config = {
-- Functions that control input lifecycle
handlers = {
-- Compute completion candidates
complete = nil,
-- Compute highlighting of current input
highlight = nil,
-- Handle input start, every key press, and input end
key = nil,
-- Show current input state
view = nil,
},
-- Default input scope: cursor/line/buffer/window/tabpage/editor/project
scope = 'editor',
}
--minidoc_afterlines_end
--- Get input from the user
---
--- # Lifecycle ~
--- *MiniInput-lifecycle*
---
--- This module implements custom lifecycle to interact with the user. It starts
--- when calling |MiniInput.get()| and ends when a value is returned.
--- Only one active input is allowed simultaneously.
---
--- The basic cycle unit is a step that processes a `key` (string or `nil`). It is
--- done by calling relevant handlers in order: key handler with `key` as a second
--- argument, highlight handler, view handler. |MiniInput.apply_handler()| is used
--- to apply each handler and the output of one is used as the input for the next.
---
--- During a step some state fields are automatically removed:
--- - <highlight> is removed before applying a key handler to always have the most
--- up to date highlighting.
--- - <complete> is removed if it was not changed after applying a key handler but
--- something else in the state besides <highlight> did change. This is meant as
--- an automatic stop of completion when it is not advancing.
---
--- If a step sets an ending state <status> (i.e. `"accept"` or `"cancel"`), the input
--- is finished by extra finishing step (see below).
---
--- The order of operations is as follows:
--- - Create initial |MiniInput-state| based on the input `opts`, with defaults
--- inferred from |MiniInput.config|, and `vim.b.miniinput_config`.
--- - Set <status> to `"start"`.
--- - Advance one step with `key=nil`. This is meant as a "setup" step for handlers.
--- - Set <status> to `"progress"`.
--- - Process `opts.init_keys` one item per step with `key` set to the string item.
--- - Wait for user to press a key (via |getcharstr()|). The key string (in escaped
--- form and not as a |key-notation|; i.e. `"\r"` and not `"<CR>"`) is then used
--- to advance a step. Note: <C-c> is hard coded to cancel the input.
--- - Repeat previous step until the ending <status> (`"accept"` or `"cancel"`).
--- - Finish the input:
--- - Perform a "teardown"' step with `key=nil`.
--- - If <errmsg> is set, throw an |error()|.
--- - If input is accepted (even if empty) and not hidden, add <input> to
--- the history. Get the whole history with |MiniInput.get_history()|.
--- - Return <input> if <status> is `"accept"`, `nil` otherwise.
---
---@param opts table|nil Options. Possible fields:
--- - <completion> `(string)` - completion method. Default: `''` to use default
--- completion method of the `complete` handler.
--- - <handlers> `(table)` - same as in |MiniInput.config.handlers|, used only for
--- the duration of the current input.
--- - <hide> `(boolean)` - whether input should be hidden. Default: `false`.
--- Note: this does not guarantee a total security of the input, only that
--- the typed characters are expected to not be shown on screen and not added
--- to the history. If set:
--- - The `view` handler is expected to not directly show current input.
--- Like replace characters with pre-defined string or fully not show.
--- - The `complete` and `highlight` handlers are not called.
--- - Accepted input will not be added to the history.
--- - <init_keys> `(table)` - array of string keys that are emulated before asking
--- for the user input. Using values that can be an output of |getcharstr()|
--- should be preferred, but a key handler should work with any string.
--- Default: `{}`.
--- - <prompt> `(string)` - intention of the input, same as in |input()|.
--- Default: `"Input"`.
--- - <scope> `(string)` - same as in |MiniInput.config|. Default: the value from
--- `MiniInput.config` with some hard coded exceptions (on Neovim<0.12.3):
--- - |vim.lsp.buf.rename()| will use `"cursor"` if no `new_name` is supplied.
---
---@return string|nil User input (from the <input> state field) if accepted, even if
--- empty. `nil` if canceled or there was an active input.
---
---@usage >lua
--- local input = MiniInput.get({
--- -- Intention of the input
--- prompt = 'New value',
--- -- The input is for something at cursor
--- scope = 'cursor',
--- -- Emulate pressing `a`, `<BS>`, and `b`
--- init_keys = { 'a', vim.keycode('<BS>'), 'b' },
--- })
--- <
MiniInput.get = function(opts)
H.check_type('opts', opts, 'table', true)
opts = opts or {}
opts.scope = opts.scope or (_G.MiniInput or {})._temp_default_scope
-- Only allow one input at a time
if H.state ~= nil then return nil end
local init_state = H.state_new(H.get_config(opts))
H.state_validate(init_state)
H.state = init_state
H.handle_step(nil)
if H.state_is_end() then return H.state_finish() end
H.state.status = 'progress'
for _, k in ipairs(H.state.opts.init_keys) do
H.handle_step(k, true)
if H.state_is_end() then return H.state_finish() end
end
H.redraw()
local lmap = H.get_lmap()
for _ = 1, 1000000 do
local key = H.getcharstr(lmap)
if key == nil and not H.state_is_end() then H.state.status = 'cancel' end
if H.state_is_end() then break end
H.handle_step(key)
if H.state_is_end() then break end
end
return H.state_finish()
end
--- A |vim.ui.input()| implementation
---
--- Function which can be used to directly override |vim.ui.input()| to use this
--- module functionality. Set automatically in |MiniInput.setup()|.
---
---@usage To preserve original `vim.ui.input()`: >lua
---
--- local ui_input_orig = vim.ui.input
--- require('mini.input').setup()
--- vim.ui.input = ui_input_orig
--- <
MiniInput.ui_input = function(opts, on_confirm)
opts = opts or {}
local input_opts = { completion = opts.completion, prompt = opts.prompt, scope = opts.scope }
input_opts.handlers = { highlight = H.make_ui_select_hl_fun(opts.highlight) }
input_opts.init_keys = { opts.default }
on_confirm(MiniInput.get(input_opts))
end
--- Get current input state
---
---@return table|nil Current |MiniInput-state| if input is active, `nil` otherwise. Notes:
--- - For hidden input (`state.opts.hide=true`), both <caret> and <input> are `nil`
--- to actually hide the input.
MiniInput.get_state = function()
if H.state == nil then return nil end
local res = H.copy_tables(H.state)
if res.opts.hide then
res.input, res.caret = nil, nil
end
return res
end
--- Get input history
---
---@return table Array with data about all previous non-hidden inputs (from earliest
--- to latest). Each element is a table with the following fields:
--- - <cwd> `(string)` - |current-directory| at the time of input's end.
--- - <input> `(string)` - input result.
--- - <prompt> `(string)` - `opts.prompt` supplied in |MiniInput.get()|.
--- - <scope> `(string)` - `opts.scope` supplied in |MiniInput.get()|.
MiniInput.get_history = function() return vim.deepcopy(H.history) end
--- Set input history
---
---@param history table Array describing all previous inputs. Same structure
--- as |MiniInput.get_history()| output.
MiniInput.set_history = function(history)
H.check_array_of('history', history, 'table')
for i, h in ipairs(history) do
local item = string.format('history[%d]', i)
H.check_type(item .. '.cwd', h.cwd, 'string')
H.check_type(item .. '.input', h.input, 'string')
H.check_type(item .. '.prompt', h.prompt, 'string')
H.check_one_of(item .. '.scope', h.scope, H.allowed_scopes)
end
H.history = vim.deepcopy(history)
end
--- Refresh active input
---
--- Performs one step of |MiniInput-lifecycle| with `key=nil`.
MiniInput.refresh = function()
if H.state == nil then return end
H.handle_step(nil)
if H.state_is_end() then H.state_finish() end
vim.schedule(H.redraw)
end
--- Highlight generators
---
--- This is a table with function elements. Call to actually get a view function.
MiniInput.gen_highlight = {}
--- Highlight with tree-sitter
---
---@param lang string A language of tree-sitter parser to use.
---
---@return function A highlight handler. Seem |MiniInput.config.handlers|.
MiniInput.gen_highlight.treesitter = function(lang)
local append_ts_hl_range = function(arr, line, tstree, tree)
local query = tstree and vim.treesitter.query.get(tree:lang(), 'highlights')
if query == nil then return end
for capture, node in query:iter_captures(tstree:root(), line) do
-- Ignore private captures
if query.captures[capture]:sub(1, 1) ~= '_' then
local _, from, _, to = node:range()
local hl = string.format('@%s.%s', query.captures[capture], query.lang)
from, to = vim.fn.charidx(line, from) + 1, vim.fn.charidx(line, to)
if from <= to then table.insert(arr, { from = from, to = to, hl = hl }) end
end
end
end
return function(state)
local line = state.input
local ok, parser = pcall(vim.treesitter.get_string_parser, line, lang)
if not ok or parser == nil then return end
-- Traverse all trees and compute highlight ranges
parser:parse(true)
local highlight = {}
parser:for_each_tree(function(tstree, tree) append_ts_hl_range(highlight, line, tstree, tree) end)
-- Respect maybe already present <highlight>
state.highlight = vim.list_extend(state.highlight or {}, highlight)
end
end
--- View generators
---
--- This is a table with function elements. Call to actually get a view function.
---
--- Each element accepts <style> option which fine tunes the input view.
--- With default key handler (|MiniInput.default_key()|) it can be adjusted
--- interactively by pressing <C-s>.
---
--- Each element also accepts <to_chunks> option. It is a function that takes
--- a |MiniInput-state| and `max_width` arguments and returns an array of `{ text, hl }`
--- chunks that fit into `max_width` display width (as in |strdisplaywidth()|).
---
--- This is a way to adjust how input is shown. Like caret/hide symbols, etc.
--- By default uses |MiniInput.state_to_chunks()|, which also means:
--- - All control characters (like literal `\t`, `\n`, etc.) will be translated
--- via |keytrans()|.
---
--- Example: >lua
---
--- local input = require('mini.input')
---
--- -- Adjust how state is converted to text-hl chunks
--- local to_chunks_opts = {
--- symbol_caret = '_',
--- symbol_hide = '*',
--- -- Do not include prompt and hint in `floatwin` handler
--- include_prompt = false,
--- include_hint = false,
--- }
--- local to_chunks = function(state, max_width)
--- return MiniInput.state_to_chunks(state, max_width, to_chunks_opts)
--- end
---
--- -- Supply custom `to_chunks` as an option
--- local view_opts = { to_chunks = to_chunks }
--- input.setup({ handlers = { view = input.gen_view.floatwin(view_opts) } })
--- <
MiniInput.gen_view = {}
--- Floating window view
---
--- Show input inside a floating window. Prompt and completion hints are shown
--- in title and footer.
---
--- Window position and dimensions are computed based on state's <scope> and
--- `opts.style`:
--- - `scope="cursor"` is shown near the cursor. For example, `style="BL"` will
--- have bottom left corner at nearest top right cell relative to the cursor.
--- - `scope="line"` is shown near the current line. For example, `style="BL"` will
--- have bottom left corner at left side above the line.
--- - `scope="buffer"` and `scope="window"` will be shown relative to the current
--- window. For example, `style="BL"` will be shown in the bottom left corner.
--- - `scope="tabpage"`, `scope="editor"`, `scope="project"` will be shown relative to
--- the overall Neovim instance. For example, `style="BL"` will be shown in the
--- bottom left corner.
---
--- Identifiers of floating window and its buffer are stored as `floatwin_win_id`
--- and `floatwin_buf_id` in |MiniInput-state| <data> field. Floating window is
--- not focused and cursor position is not the same as caret position.
---
---@param opts table|nil Options. Possible fields:
--- - <adjust_config> `(function)` - function to adjust default config. Will be
--- called with two arguments: current |MiniInput-state| and a window config
--- (always with `relative="editor"` and `anchor="NW"`) computed based
--- on `opts.style`. Should return an adjusted window config.
--- Default: `function(state, config) return config end`.
---
--- - <style> `(string)` - a two character description of how to show the window.
--- Default: `"BL"`.
---
--- First character describes vertical position:
--- - `"T"` - top window border will be at scope's reference top border.
--- - `"M"` - middle between top and bottom window borders will be at the
--- middle of scope's reference top and bottom borders.
--- - `"B"` - bottom window border will be at scope's reference bottom border.
---
--- Second character describes horizontal position:
--- - `"L"` - left window border will be at scope's reference left border.
--- - `"M"` - middle between left and right window borders will be at the
--- middle of scope's reference left and right borders.
--- - `"R"` - right window border will be at scope's reference right border.
---
--- __input_to_chunks
---
---@usage >lua
--- local input = require('mini.input')
---
--- -- Use border different from 'winborder'
--- local adjust_config = function(_, config)
--- config.border = 'double'
--- return config
--- end
---
--- -- Choose initial style based on the scope
--- local floatwin = input.gen_view.floatwin
--- local view_tm = floatwin({ style = 'TM', adjust_config = adjust_config })
--- local view_bl = floatwin({ style = 'BL', adjust_config = adjust_config })
--- local view_handler = function(state)
--- local scope, view = state.opts.scope, view_tm
--- if scope == 'cursor' or scope == 'line' then view = view_bl end
--- return view(state)
--- end
---
--- input.setup({ handlers = { view = view_handler } })
--- <
MiniInput.gen_view.floatwin = function(opts)
local default_opts = { style = 'BL' }
default_opts.adjust_config = function(_, config) return config end
local default_to_chunks_opts = { include_prompt = false, include_hint = false }
default_opts.to_chunks = function(state, max_width)
return MiniInput.state_to_chunks(state, max_width, default_to_chunks_opts)
end
opts = vim.tbl_extend('force', default_opts, opts or {})
H.check_type('opts.adjust_config', opts.adjust_config, 'callable')
H.check_one_of('opts.style', opts.style, { 'TL', 'TM', 'TR', 'ML', 'MM', 'MR', 'BL', 'BM', 'BR' })
H.check_type('opts.to_chunks', opts.to_chunks, 'callable')
-- Change style in vertical-horizontal order
local all_styles = { 'BL', 'ML', 'TL', 'BM', 'MM', 'TM', 'BR', 'MR', 'TR' }
return function(state)
if H.state_is_end(state) then
pcall(vim.api.nvim_win_close, state.data.floatwin_win_id, true)
pcall(vim.api.nvim_buf_delete, state.data.floatwin_buf_id, { force = true })
return
end
local style, _ = H.handle_view_style(state, opts.style, all_styles)
-- Try to fit all chunks first, but later still truncate to window width
local winborder = vim.fn.exists('+winborder') == 0 and '' or vim.o.winborder
default_to_chunks_opts = { include_prompt = winborder == 'none', include_hint = winborder == 'none' }
local chunks = H.get_chunks(opts, state)
local default_config = H.default_floatwin_config(state, style, H.get_chunks_displaywidth(chunks))
local config = opts.adjust_config(state, default_config)
chunks = H.get_chunks(opts, state, config.width)
H.ensure_floatwin_buf(state, chunks)
H.ensure_floatwin_win(state, config)
end
end
--- UI line (statusline, tabline, winbar) view
---
---@param opts table|nil Options. Possible fields:
--- - <style> `(string)` - which UI line to use. One of `"statusline"`,
--- `"tabline"`, `"winbar"`. Default: `"statusline"`.
---
--- __input_to_chunks
---
---@usage >lua
--- local input = require('mini.input')
---
--- -- Choose initial style based on the scope
--- local view_tabline = input.gen_view.uiline({ style = 'tabline' })
--- local view_winbar = input.gen_view.uiline({ style = 'winbar' })
--- local view_handler = function(state)
--- local scope, view = state.opts.scope, view_winbar
--- if scope == 'tabpage' or scope == 'editor' or scope == 'project' then
--- view = view_tabline
--- end
--- return view(state)
--- end
---
--- input.setup({ handlers = { view = view_handler } })
--- <
MiniInput.gen_view.uiline = function(opts)
opts = vim.tbl_extend('force', { style = 'statusline', to_chunks = MiniInput.state_to_chunks }, opts or {})
H.check_one_of('opts.style', opts.style, { 'statusline', 'tabline', 'winbar' })
H.check_type('opts.to_chunks', opts.to_chunks, 'callable')
local all_styles = { 'statusline', 'winbar', 'tabline' }
local escape_stl = function(x) return (x:gsub('%%', '%%%%')) end
return function(state)
local style, style_is_new = H.handle_view_style(state, opts.style, all_styles)
H.uiline_handle_option_values(state, style_is_new)
if H.state_is_end(state) then return end
local max_width = style == 'tabline' and vim.o.columns or vim.fn.winwidth(0)
local chunks = H.get_chunks(opts, state, max_width)
local parts = vim.tbl_map(function(c) return '%#' .. escape_stl(c[2]) .. '#' .. escape_stl(c[1]) end, chunks)
local uiline_value = table.concat(parts) .. '%#MiniInputNormal#'
local opt = style == 'tabline' and vim.o or vim.wo
opt[style] = uiline_value
if style == 'statusline' and vim.o.laststatus < 2 then vim.o.laststatus = 2 end
if style == 'tabline' and vim.o.showtabline < 2 then vim.o.showtabline = 2 end
end
end
--- Virtual (line, text) view
---
---@param opts table|nil Options. Possible fields:
--- - <style> `(string)` - how to display virtual text. One of `"above"`, `"below"`,
--- `"inline"`. Default: `"above"`.
---
--- __input_to_chunks
---
---@usage >lua
--- -- Choose different initial style
--- local input = require('mini.input')
--- local view_handler = input.gen_view.virtual({ style = 'inline' })
--- input.setup({ handlers = { view = view_handler } })
--- <
MiniInput.gen_view.virtual = function(opts)
opts = vim.tbl_extend('force', { style = 'above', to_chunks = MiniInput.state_to_chunks }, opts or {})
H.check_one_of('opts.style', opts.style, { 'above', 'below', 'inline' })
H.check_type('opts.to_chunks', opts.to_chunks, 'callable')
local ns_id = H.ns_id.view
local all_styles = { 'above', 'below', 'inline' }
return function(state)
-- Cleanup
local buf_id, extmark_id = vim.api.nvim_get_current_buf(), state.data.extmark_id
if H.state_is_end(state) then
pcall(vim.api.nvim_buf_del_extmark, state.data.buf_id, ns_id, extmark_id)
return
end
local ok_extmark = pcall(vim.api.nvim_get_extmark_by_id, state.data.buf_id, ns_id, extmark_id, {})
if not (buf_id == state.data.buf_id and ok_extmark) then
pcall(vim.api.nvim_buf_del_extmark, state.data.buf_id, ns_id, extmark_id)
end
state.data.buf_id = buf_id
local style, style_is_new = H.handle_view_style(state, opts.style, all_styles)
local is_virtline = style == 'above' or style == 'below'
-- Get chunks
local win_info = H.get_curwin_info()
local max_width = win_info.width - win_info.textoff
local chunks = H.get_chunks(opts, state, max_width)
local chunks_width = H.get_chunks_displaywidth(chunks)
if is_virtline and chunks_width < max_width then
chunks = vim.deepcopy(chunks)
table.insert(chunks, { string.rep(' ', max_width - chunks_width), 'MiniInputNormal' })
end
-- Set
local extmark_opts = { id = state.data.extmark_id, priority = 4096 }
if is_virtline then
extmark_opts.virt_lines = { chunks }
extmark_opts.virt_lines_above = style == 'above'
else
extmark_opts.virt_text = chunks
extmark_opts.virt_text_pos = 'inline'
end
local cur_pos = vim.api.nvim_win_get_cursor(0)
state.data.extmark_id = vim.api.nvim_buf_set_extmark(0, ns_id, cur_pos[1] - 1, cur_pos[2], extmark_opts)
-- Ensure that both current and virtual lines are visible
if is_virtline and (state.status == 'start' or style_is_new) then
local win_line, win_height = vim.fn.winline(), vim.fn.winheight(0)
if win_line == 1 and style == 'above' then vim.cmd('normal! \25') end
-- With 'above' the cursor line is moved down, with 'below' - no move
-- local is_below_screen = style == 'above' and win_line > win_height or win_line >= win_height
local is_below_screen = (win_line - (style == 'above' and 1 or 0)) >= win_height
if is_below_screen then vim.cmd('normal! \5') end
end
end
end
--- Default key handler
---
--- Emulates most of |Command-line-mode| editing (|cmdline-editing|):
---
--- - Accept: <CR>. To insert literal newline, type `<C-j>`.
---
--- - Cancel: <Esc> or <C-c>.
---
--- - Move caret:
--- - <Left>, <Right> - one character to left / right.
--- - <M-h>, <M-l> - one character to left / right.
--- - <S-Left>, <S-Right> - one word to left / right.
--- - <C-b>, <C-e> (if no completion) - to start / end of input.
--- - <Home>, <End> - to start / end of input.
---
--- - Delete:
--- - <BS> / <C-h> - to caret's left. If `opts.autopair` is enabled, also delete
--- a character to caret's right if it formed a respected character pair.
--- See "Autopair".
--- - <Del> - at caret.
--- - <C-u> - from start to caret. As |c_CTRL-U|.
--- - <C-w> - contiguous keyword or non-keyword to caret's left. As |c_CTRL-W|.
---
--- - Insert at caret:
--- - <C-k> - digraph based on the next two pressed keys. As |c_CTRL-K|.
--- - <C-r> - content of a register. As |c_CTRL-R| including support for
--- special <C-a>, <C-f>, <C-l>, <C-w> keys for a register.
--- - <C-v>, <C-q> - next key literally. As |c_CTRL-V| and |i_CTRL-V_digit|
--- (all digits must be typed in full or stopped with <C-c>).
--- - Pasting from system |clipboard| is supported for "non-streaming" paste (as
--- described in |vim.paste()|).
---
--- - Autopair (if `opts.autopair` is set) is similar to |mini.pairs|:
--- - Opening characters `(`, `[`, `{` always insert a `()`, `[]`, `{}` pair and places
--- caret inside of it.
--- - Closing characters `)`, `]`, `}` move caret to the right if there is the same
--- character to the right.
--- - Closeopen characters `'`, `"`, <`> perform "close" action if possible and
--- "open" action if not.
--- - In all cases press <C-v> before special character to insert it verbatim.
---
--- - Completion:
--- - <Tab>, <S-Tab> - start with `state.opts.completion` method if not active
--- and advance through active completion (i.e. replace currently displayed
--- at caret item with the next one).
--- Note: type `<C-v><Tab>` to insert literal `\t`.
--- - <C-n>, <C-p>, <Up>, <Down> - start with `"history"` method if not active
--- and advance through active completion.
--- - <C-e> - cancel and return to initial input and caret.
--- - <C-y> - accept current candidate. Note: it will also be accepted after
--- any key that doesn't advance completion.
---
--- - Miscellaneous:
--- - <C-o> - change scope of the input. Cycles through all available ones.
--- - <C-s> - change view style. Works only with |MiniInput.gen_view| view
--- handlers. Cycles through all available ones.
--- - <C-x> - toggle hide/unhide of the input.
---
--- - Special keys (combo, mouse, but not whitespace) not listed above - ignored.
--- Anything else (even more than a single character) - inserted at caret.