-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbasics.lua
757 lines (672 loc) · 31.4 KB
/
basics.lua
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
--- *mini.basics* Common configuration presets
--- *MiniBasics*
---
--- MIT License Copyright (c) 2023 Evgeni Chasnovski
---
--- ==============================================================================
---
--- Install, create 'init.lua', add `require('mini.basics').setup()` and you
--- are good to go.
---
--- Features:
--- - Presets for common options. It will only change option if it wasn't
--- manually set before. See more in |MiniBasics.config.options|.
---
--- - Presets for common mappings. It will only add a mapping if it wasn't
--- manually created before. See more in |MiniBasics.config.mappings|.
---
--- - Presets for common autocommands. See more in |MiniBasics.config.autocommands|.
---
--- - Reverse compatibility is a high priority. Any decision to change already
--- present behavior will be made with great care.
---
--- Notes:
--- - Main goal of this module is to provide a relatively easier way for
--- new-ish Neovim users to have better "works out of the box" experience
--- while having documented relevant options/mappings/autocommands to study.
--- It is based partially on survey among Neovim users and partially is
--- coming from personal preferences.
---
--- However, more seasoned users almost surely will find something useful.
---
--- Still, it is recommended to read about used options/mappings/autocommands
--- and decide if they are needed. The main way to do that is by reading
--- Neovim's help pages (linked in help file) and this module's source code
--- (thoroughly documented for easier comprehension).
---
--- # Setup ~
---
--- This module needs a setup with `require('mini.basics').setup({})` (replace
--- `{}` with your `config` table). It will create global Lua table `MiniBasics`
--- which you can use for scripting or manually (with `:lua MiniBasics.*`).
---
--- See |MiniBasics.config| for available config settings.
---
--- To stop module from showing non-error feedback, set `config.silent = true`.
---
--- # Comparisons ~
---
--- - 'tpope/vim-sensible':
--- - Most of 'tpope/vim-sensible' is already incorporated as default
--- options in Neovim (see |nvim-default|). This module has a much
--- broader effect.
--- - 'tpope/vim-unimpaired':
--- - The 'tpope/vim-unimpaired' has mapping for toggling options with `yo`
--- prefix. This module implements similar functionality with `\` prefix
--- (see |MiniBasics.config.mappings|).
---@diagnostic disable:undefined-field
-- To study source behind presets, search for:
-- - `-- Options ---` for `config.options`.
-- - `-- Mappings ---` for `config.mappings`.
-- - `-- Autocommands ---` for `config.autocommands`.
-- Module definition ==========================================================
local MiniBasics = {}
local H = {}
--- Module setup
---
---@param config table|nil Module config table. See |MiniBasics.config|.
---
---@usage `require('mini.basics').setup({})` (replace `{}` with your `config` table)
MiniBasics.setup = function(config)
-- Export module
_G.MiniBasics = MiniBasics
-- Setup config
config = H.setup_config(config)
-- Apply config
H.apply_config(config)
end
--- Module config
---
--- Default values:
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
---@text *MiniBasics.config.options*
--- # Options ~
---
--- Usage example: >
--- require('mini.basics').setup({
--- options = {
--- basic = true,
--- extra_ui = true,
--- win_borders = 'double',
--- }
--- })
---
--- ## options.basic ~
---
--- The `config.options.basic` sets certain options to values which are quite
--- commonly used (judging by study of available Neovim pre-configurations,
--- public dotfiles, and surveys).
--- Any option is changed only if it was not set manually beforehand.
--- For exact changes, please see source code ('lua/mini/basics.lua').
---
--- Here is the list of affected options (put cursor on it and press |CTRL-]|):
--- - General:
--- - Sets |<Leader>| key to |<Space>|. Be sure to make all Leader mappings
--- after this (otherwise they are made with default <Leader>).
--- - Runs `:filetype plugin indent on` (see |:filetype-overview|)
--- - |backup|
--- - |mouse|
--- - |undofile|
--- - |writebackup|
--- - Appearance
--- - |breakindent|
--- - |cursorline|
--- - |fillchars|
--- - |linebreak|
--- - |number|
--- - |ruler|
--- - |showmode|
--- - |signcolumn|
--- - |shortmess|
--- - |splitbelow|
--- - |splitkeep| (on Neovim>=0.9)
--- - |splitright|
--- - |termguicolors| (on Neovim<0.10; later versions have it smartly enabled)
--- - |wrap|
--- - Editing
--- - |completeopt|
--- - |formatoptions|
--- - |ignorecase|
--- - |incsearch|
--- - |infercase|
--- - |smartcase|
--- - |smartindent|
--- - |virtualedit|
---
--- ## options.extra_ui ~
---
--- The `config.options.extra_ui` sets certain options for visual appearance
--- which might not be aligned with common preferences, but still worth trying.
--- Any option is changed only if it was not set manually beforehand.
--- For exact changes, please see source code ('lua/mini/basics.lua').
---
--- List of affected options:
--- - |list|
--- - |listchars|
--- - |pumblend|
--- - |pumheight|
--- - |winblend|
--- - Runs `:syntax on` (see |:syntax-on|)
---
--- ## options.win_borders
---
--- The `config.options.win_borders` updates |fillchars| to have a consistent set of
--- characters for window border (`vert`, `horiz`, etc.).
---
--- Available values:
--- - `'bold'` - bold lines.
--- - `'dot'` - dot in every cell.
--- - `'double'` - double line.
--- - `'single'` - single line.
--- - `'solid'` - no symbol, only background.
---
--- *MiniBasics.config.mappings*
--- # Mappings ~
---
--- Usage example: >
--- require('mini.basics').setup({
--- mappings = {
--- basic = true,
--- option_toggle_prefix = [[\]],
--- windows = true,
--- move_with_alt = true,
--- }
--- })
---
--- If you don't want only some mappings to be made at all, use |vim.keymap.del()|
--- after calling |MiniMisc.setup()|. For example, to delete `<C-w>` mapping in
--- |Terminal-mode| (as it conflicts with `<C-w>` usage in terminal emulators)
--- use this code: >
---
--- vim.keymap.del('t', '<C-w>').
---
--- ## mappings.basic ~
---
--- The `config.mappings.basic` creates mappings for certain commonly mapped actions
--- (judging by study of available Neovim pre-configurations and public dotfiles).
---
--- Some of the mappings override built-in ones to either improve their
--- behavior or override its default not very useful action.
--- It will only add a mapping if it wasn't manually created before.
---
--- Here is a table with created mappings : >
---
--- |Keys | Modes | Description |
--- |-------|-----------------|-----------------------------------------------|
--- | j | Normal, Visual | Move down by visible lines with no [count] |
--- | k | Normal, Visual | Move up by visible lines with no [count] |
--- | go | Normal | Add [count] empty lines after cursor |
--- | gO | Normal | Add [count] empty lines before cursor |
--- | gy | Normal, Visual | Copy to system clipboard |
--- | gp | Normal, Visual | Paste from system clipboard |
--- | gV | Normal | Visually select latest changed or yanked text |
--- | g/ | Visual | Search inside current visual selection |
--- | * | Visual | Search forward for current visual selection |
--- | # | Visual | Search backward for current visual selection |
--- | <C-s> | Normal, Visual, | Save and go to Normal mode |
--- | | Insert | |
--- <
--- Notes:
--- - See |[count]| for its meaning.
--- - On Neovim>=0.10 mappings for `#` and `*` are not created as their
--- enhanced variants are made built-in. See |v_star-default| and |v_#-default|.
---
--- ## mappings.option_toggle_prefix ~
---
--- The `config.mappings.option_toggle_prefix` defines a prefix used for
--- creating mappings that toggle common options. The result mappings will be
--- `<prefix> + <suffix>`. For example, with default value, `\w` will toggle |wrap|.
---
--- Other viable choices for prefix are
--- - `,` (as a mnemonic for several values to toggle).
--- - `|` (as a same mnemonic).
--- - `yo` (used in 'tpope/vim-unimpaired')
--- - Something with |<Leader>| key, like `<Leader>t` (`t` for "toggle"). Note:
--- if your prefix contains `<Leader>` key, make sure to set it before
--- calling |MiniBasics.setup()| (as is done with default `basic` field of
--- |MiniBasics.config.options|).
---
--- After toggling, there will be a feedback about the current option value if
--- prior to `require('mini.basics').setup()` module wasn't silenced (see
--- "Silencing" section in |mini.basics|).
---
--- It will only add a mapping if it wasn't manually created before.
---
--- Here is a list of suffixes for created toggling mappings (all in Normal mode):
---
--- - `b` - |'background'|.
--- - `c` - |'cursorline'|.
--- - `C` - |'cursorcolumn'|.
--- - `d` - diagnostic (via |vim.diagnostic.enable()| and |vim.diagnostic.disable()|).
--- - `h` - |'hlsearch'| (or |v:hlsearch| to be precise).
--- - `i` - |'ignorecase'|.
--- - `l` - |'list'|.
--- - `n` - |'number'|.
--- - `r` - |'relativenumber'|.
--- - `s` - |'spell'|.
--- - `w` - |'wrap'|.
---
--- ## mappings.windows ~
---
--- The `config.mappings.windows` creates mappings for easiere window manipulation.
---
--- It will only add a mapping if it wasn't manually created before.
---
--- Here is a list with created Normal mode mappings (all mappings respect |[count]|):
--- - Window navigation:
--- - `<C-h>` - focus on left window (see |CTRL-W_H|).
--- - `<C-j>` - focus on below window (see |CTRL-W_J|).
--- - `<C-k>` - focus on above window (see |CTRL-W_K|).
--- - `<C-l>` - focus on right window (see |CTRL-W_L|).
--- - Window resize (all use arrow keys; variants of |resize|; all respect |[count]|):
--- - `<C-left>` - decrease window width.
--- - `<C-down>` - decrease window height.
--- - `<C-up>` - increase window height.
--- - `<C-right>` - increase window width.
---
--- ## mappings.move_with_alt
---
--- The `config.mappings.move_with_alt` creates mappings for a more consistent
--- cursor move in Insert, Command, and Terminal modes. For example, it proves
--- useful in combination of autopair plugin (like |MiniPairs|) to move right
--- outside of inserted pairs (no matter what the pair is).
---
--- It will only add a mapping if it wasn't manually created before.
---
--- Here is a list of created mappings (`<M-x>` means `Alt`/`Meta` plus `x`):
--- - `<M-h>` - move cursor left. Modes: Insert, Terminal, Command.
--- - `<M-j>` - move cursor down. Modes: Insert, Terminal.
--- - `<M-k>` - move cursor up. Modes: Insert, Terminal.
--- - `<M-l>` - move cursor right. Modes: Insert, Terminal, Command.
---
--- *MiniBasics.config.autocommands*
--- # Autocommands ~
---
--- Usage example: >
--- require('mini.basics').setup({
--- autocommands = {
--- basic = true,
--- relnum_in_visual_mode = true,
--- }
--- })
---
--- ## autocommands.basic ~
---
--- The `config.autocommands.basic` creates some common autocommands:
---
--- - Starts insert mode when opening terminal (see |startinsert| and |TermOpen|).
--- - Highlights yanked text for a brief period of time (see
--- |vim.highlight.on_yank()| and |TextYankPost|).
---
--- ## autocommands.relnum_in_visual_mode ~
---
--- The `config.autocommands.relnum_in_visual_mode` creates autocommands that
--- enable |relativenumber| in linewise and blockwise Visual modes and disable
--- otherwise. See |ModeChanged|.
MiniBasics.config = {
-- Options. Set to `false` to disable.
options = {
-- Basic options ('number', 'ignorecase', and many more)
basic = true,
-- Extra UI features ('winblend', 'cmdheight=0', ...)
extra_ui = false,
-- Presets for window borders ('single', 'double', ...)
win_borders = 'default',
},
-- Mappings. Set to `false` to disable.
mappings = {
-- Basic mappings (better 'jk', save with Ctrl+S, ...)
basic = true,
-- Prefix for mappings that toggle common options ('wrap', 'spell', ...).
-- Supply empty string to not create these mappings.
option_toggle_prefix = [[\]],
-- Window navigation with <C-hjkl>, resize with <C-arrow>
windows = false,
-- Move cursor in Insert, Command, and Terminal mode with <M-hjkl>
move_with_alt = false,
},
-- Autocommands. Set to `false` to disable
autocommands = {
-- Basic autocommands (highlight on yank, start Insert in terminal, ...)
basic = true,
-- Set 'relativenumber' only in linewise and blockwise Visual mode
relnum_in_visual_mode = false,
},
-- Whether to disable showing non-error feedback
silent = false,
}
--minidoc_afterlines_end
--- Toggle diagnostic for current buffer
---
--- This uses |vim.diagnostic.enable()| and |vim.diagnostic.disable()| on
--- per buffer basis.
---
---@return string String indicator for new state. Similar to what |:set| `{option}?` shows.
MiniBasics.toggle_diagnostic = function()
local buf_id = vim.api.nvim_get_current_buf()
local is_enabled = H.diagnostic_is_enabled(buf_id)
local f = is_enabled and vim.diagnostic.disable or vim.diagnostic.enable
f(buf_id)
local new_buf_state = not is_enabled
H.buffer_diagnostic_state[buf_id] = new_buf_state
return new_buf_state and ' diagnostic' or 'nodiagnostic'
end
-- Helper data ================================================================
-- Module default config
H.default_config = vim.deepcopy(MiniBasics.config)
-- Diagnostic state per buffer
H.buffer_diagnostic_state = {}
-- Helper functionality =======================================================
-- Settings -------------------------------------------------------------------
H.setup_config = function(config)
-- General idea: if some table elements are not present in user-supplied
-- `config`, take them from default config
vim.validate({ config = { config, 'table', true } })
config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
vim.validate({
options = { config.options, 'table' },
mappings = { config.mappings, 'table' },
autocommands = { config.autocommands, 'table' },
})
vim.validate({
['options.basic'] = { config.options.basic, 'boolean' },
['options.extra_ui'] = { config.options.extra_ui, 'boolean' },
['options.win_borders'] = { config.options.win_borders, 'string' },
['mappings.basic'] = { config.mappings.basic, 'boolean' },
['mappings.option_toggle_prefix'] = { config.mappings.option_toggle_prefix, 'string' },
['mappings.windows'] = { config.mappings.windows, 'boolean' },
['mappings.move_with_alt'] = { config.mappings.move_with_alt, 'boolean' },
['autocommands.basic'] = { config.autocommands.basic, 'boolean' },
['autocommands.relnum_in_visual_mode'] = { config.autocommands.relnum_in_visual_mode, 'boolean' },
['silent'] = { config.silent, 'boolean' },
})
return config
end
H.apply_config = function(config)
MiniBasics.config = config
H.apply_options(config)
H.apply_mappings(config)
H.apply_autocommands(config)
end
-- Options --------------------------------------------------------------------
--stylua: ignore
H.apply_options = function(config)
-- Use `local o, opt = vim.o, vim.opt` to copy lines as is.
-- Or use `vim.o` and `vim.opt` directly.
local o, opt = H.vim_o, H.vim_opt
-- Basic options
if config.options.basic then
-- Leader key
if vim.g.mapleader == nil then
vim.g.mapleader = ' ' -- Use space as the one and only true Leader key
end
-- General
o.undofile = true -- Enable persistent undo (see also `:h undodir`)
o.backup = false -- Don't store backup while overwriting the file
o.writebackup = false -- Don't store backup while overwriting the file
o.mouse = 'a' -- Enable mouse for all available modes
vim.cmd('filetype plugin indent on') -- Enable all filetype plugins
-- Appearance
o.breakindent = true -- Indent wrapped lines to match line start
o.cursorline = true -- Highlight current line
o.linebreak = true -- Wrap long lines at 'breakat' (if 'wrap' is set)
o.number = true -- Show line numbers
o.splitbelow = true -- Horizontal splits will be below
o.splitright = true -- Vertical splits will be to the right
o.ruler = false -- Don't show cursor position in command line
o.showmode = false -- Don't show mode in command line
o.wrap = false -- Display long lines as just one line
o.signcolumn = 'yes' -- Always show sign column (otherwise it will shift text)
o.fillchars = 'eob: ' -- Don't show `~` outside of buffer
-- Editing
o.ignorecase = true -- Ignore case when searching (use `\C` to force not doing that)
o.incsearch = true -- Show search results while typing
o.infercase = true -- Infer letter cases for a richer built-in keyword completion
o.smartcase = true -- Don't ignore case when searching if pattern has upper case
o.smartindent = true -- Make indenting smart
o.completeopt = 'menuone,noinsert,noselect' -- Customize completions
o.virtualedit = 'block' -- Allow going past the end of line in visual block mode
o.formatoptions = 'qjl1' -- Don't autoformat comments
-- Neovim version dependent
if vim.fn.has('nvim-0.9') == 1 then
opt.shortmess:append('WcC') -- Reduce command line messages
o.splitkeep = 'screen' -- Reduce scroll during window split
else
opt.shortmess:append('Wc') -- Reduce command line messages
end
if vim.fn.has('nvim-0.10') == 0 then
o.termguicolors = true -- Enable gui colors
end
end
-- Some opinioneted extra UI options
if config.options.extra_ui then
o.pumblend = 10 -- Make builtin completion menus slightly transparent
o.pumheight = 10 -- Make popup menu smaller
o.winblend = 10 -- Make floating windows slightly transparent
-- NOTE: Having `tab` present is needed because `^I` will be shown if
-- omitted (documented in `:h listchars`).
-- Having it equal to a default value should be less intrusive.
o.listchars = 'tab:> ,extends:…,precedes:…,nbsp:␣' -- Define which helper symbols to show
o.list = true -- Show some helper symbols
-- Enable syntax highlighting if it wasn't already (as it is time consuming)
if vim.fn.exists("syntax_on") ~= 1 then vim.cmd([[syntax enable]]) end
end
-- Use some common window borders presets
local border_chars = H.win_borders_fillchars[config.options.win_borders]
if border_chars ~= nil then
vim.opt.fillchars:append(border_chars)
end
end
H.vim_o = setmetatable({}, {
__newindex = function(_, name, value)
local was_set = vim.api.nvim_get_option_info(name).was_set
if was_set then return end
vim.o[name] = value
end,
})
H.vim_opt = setmetatable({}, {
__index = function(_, name)
local was_set = vim.api.nvim_get_option_info(name).was_set
if was_set then return { append = function() end, remove = function() end } end
return vim.opt[name]
end,
})
--stylua: ignore
H.win_borders_fillchars = {
bold = 'vert:┃,horiz:━,horizdown:┳,horizup:┻,verthoriz:╋,vertleft:┫,vertright:┣',
dot = 'vert:·,horiz:·,horizdown:·,horizup:·,verthoriz:·,vertleft:·,vertright:·',
double = 'vert:║,horiz:═,horizdown:╦,horizup:╩,verthoriz:╬,vertleft:╣,vertright:╠',
single = 'vert:│,horiz:─,horizdown:┬,horizup:┴,verthoriz:┼,vertleft:┤,vertright:├',
solid = 'vert: ,horiz: ,horizdown: ,horizup: ,verthoriz: ,vertleft: ,vertright: ',
}
-- Mappings -------------------------------------------------------------------
--stylua: ignore
H.apply_mappings = function(config)
-- Use `local map = vim.keymap.set` to copy lines as is. Or use it directly.
local map = H.keymap_set
if config.mappings.basic then
-- Move by visible lines. Notes:
-- - Don't map in Operator-pending mode because it severely changes behavior:
-- like `dj` on non-wrapped line will not delete it.
-- - Condition on `v:count == 0` to allow easier use of relative line numbers.
map({ 'n', 'x' }, 'j', [[v:count == 0 ? 'gj' : 'j']], { expr = true })
map({ 'n', 'x' }, 'k', [[v:count == 0 ? 'gk' : 'k']], { expr = true })
-- Add empty lines before and after cursor line supporting dot-repeat
MiniBasics.put_empty_line = function(put_above)
-- This has a typical workflow for enabling dot-repeat:
-- - On first call it sets `operatorfunc`, caches data, and calls
-- `operatorfunc` on current cursor position.
-- - On second call it performs task: puts `v:count1` empty lines
-- above/below current line.
if type(put_above) == 'boolean' then
vim.o.operatorfunc = 'v:lua.MiniBasics.put_empty_line'
MiniBasics.cache_empty_line = { put_above = put_above }
return 'g@l'
end
local target_line = vim.fn.line('.') - (MiniBasics.cache_empty_line.put_above and 1 or 0)
vim.fn.append(target_line, vim.fn['repeat']({ '' }, vim.v.count1))
end
-- NOTE: if you don't want to support dot-repeat, use this snippet:
-- ```
-- map('n', 'gO', "<Cmd>call append(line('.') - 1, repeat([''], v:count1))<CR>")
-- map('n', 'go', "<Cmd>call append(line('.'), repeat([''], v:count1))<CR>")
-- ```
map('n', 'gO', 'v:lua.MiniBasics.put_empty_line(v:true)', { expr = true, desc = 'Put empty line above' })
map('n', 'go', 'v:lua.MiniBasics.put_empty_line(v:false)', { expr = true, desc = 'Put empty line below' })
-- Copy/paste with system clipboard
map({ 'n', 'x' }, 'gy', '"+y', { desc = 'Copy to system clipboard' })
map( 'n', 'gp', '"+p', { desc = 'Paste from system clipboard' })
-- - Paste in Visual with `P` to not copy selected text (`:h v_P`)
map( 'x', 'gp', '"+P', { desc = 'Paste from system clipboard' })
-- Reselect latest changed, put, or yanked text
map('n', 'gV', '"`[" . strpart(getregtype(), 0, 1) . "`]"', { expr = true, replace_keycodes = false, desc = 'Visually select changed text' })
-- Search inside visually highlighted text. Use `silent = false` for it to
-- make effect immediately.
map('x', 'g/', '<esc>/\\%V', { silent = false, desc = 'Search inside visual selection' })
-- Search visually selected text (slightly better than builtins in
-- Neovim>=0.8 but slightly worse than builtins in Neovim>=0.10)
-- TODO: Remove this after compatibility with Neovim=0.9 is dropped
if vim.fn.has('nvim-0.10') == 0 then
map('x', '*', [[y/\V<C-R>=escape(@", '/\')<CR><CR>]], { desc = 'Search forward' })
map('x', '#', [[y?\V<C-R>=escape(@", '?\')<CR><CR>]], { desc = 'Search backward' })
end
-- Alternative way to save and exit in Normal mode.
-- NOTE: Adding `redraw` helps with `cmdheight=0` if buffer is not modified
map( 'n', '<C-S>', '<Cmd>silent! update | redraw<CR>', { desc = 'Save' })
map({ 'i', 'x' }, '<C-S>', '<Esc><Cmd>silent! update | redraw<CR>', { desc = 'Save and go to Normal mode' })
end
local toggle_prefix = config.mappings.option_toggle_prefix
if type(toggle_prefix) == 'string' and toggle_prefix ~= '' then
local map_toggle = function(lhs, rhs, desc) map('n', toggle_prefix .. lhs, rhs, { desc = desc }) end
if config.silent then
-- Toggle without feedback
map_toggle('b', '<Cmd>lua vim.o.bg = vim.o.bg == "dark" and "light" or "dark"<CR>', "Toggle 'background'")
map_toggle('c', '<Cmd>setlocal cursorline!<CR>', "Toggle 'cursorline'")
map_toggle('C', '<Cmd>setlocal cursorcolumn!<CR>', "Toggle 'cursorcolumn'")
map_toggle('d', '<Cmd>lua MiniBasics.toggle_diagnostic()<CR>', 'Toggle diagnostic')
map_toggle('h', '<Cmd>let v:hlsearch = 1 - v:hlsearch<CR>', 'Toggle search highlight')
map_toggle('i', '<Cmd>setlocal ignorecase!<CR>', "Toggle 'ignorecase'")
map_toggle('l', '<Cmd>setlocal list!<CR>', "Toggle 'list'")
map_toggle('n', '<Cmd>setlocal number!<CR>', "Toggle 'number'")
map_toggle('r', '<Cmd>setlocal relativenumber!<CR>', "Toggle 'relativenumber'")
map_toggle('s', '<Cmd>setlocal spell!<CR>', "Toggle 'spell'")
map_toggle('w', '<Cmd>setlocal wrap!<CR>', "Toggle 'wrap'")
else
map_toggle('b', '<Cmd>lua vim.o.bg = vim.o.bg == "dark" and "light" or "dark"; print(vim.o.bg)<CR>', "Toggle 'background'")
map_toggle('c', '<Cmd>setlocal cursorline! cursorline?<CR>', "Toggle 'cursorline'")
map_toggle('C', '<Cmd>setlocal cursorcolumn! cursorcolumn?<CR>', "Toggle 'cursorcolumn'")
map_toggle('d', '<Cmd>lua print(MiniBasics.toggle_diagnostic())<CR>', 'Toggle diagnostic')
map_toggle('h', '<Cmd>let v:hlsearch = 1 - v:hlsearch | echo (v:hlsearch ? " " : "no") . "hlsearch"<CR>', 'Toggle search highlight')
map_toggle('i', '<Cmd>setlocal ignorecase! ignorecase?<CR>', "Toggle 'ignorecase'")
map_toggle('l', '<Cmd>setlocal list! list?<CR>', "Toggle 'list'")
map_toggle('n', '<Cmd>setlocal number! number?<CR>', "Toggle 'number'")
map_toggle('r', '<Cmd>setlocal relativenumber! relativenumber?<CR>', "Toggle 'relativenumber'")
map_toggle('s', '<Cmd>setlocal spell! spell?<CR>', "Toggle 'spell'")
map_toggle('w', '<Cmd>setlocal wrap! wrap?<CR>', "Toggle 'wrap'")
end
end
if config.mappings.windows then
-- Window navigation
map('n', '<C-H>', '<C-w>h', { desc = 'Focus on left window' })
map('n', '<C-J>', '<C-w>j', { desc = 'Focus on below window' })
map('n', '<C-K>', '<C-w>k', { desc = 'Focus on above window' })
map('n', '<C-L>', '<C-w>l', { desc = 'Focus on right window' })
-- Window resize (respecting `v:count`)
map('n', '<C-Left>', '"<Cmd>vertical resize -" . v:count1 . "<CR>"', { expr = true, replace_keycodes = false, desc = 'Decrease window width' })
map('n', '<C-Down>', '"<Cmd>resize -" . v:count1 . "<CR>"', { expr = true, replace_keycodes = false, desc = 'Decrease window height' })
map('n', '<C-Up>', '"<Cmd>resize +" . v:count1 . "<CR>"', { expr = true, replace_keycodes = false, desc = 'Increase window height' })
map('n', '<C-Right>', '"<Cmd>vertical resize +" . v:count1 . "<CR>"', { expr = true, replace_keycodes = false, desc = 'Increase window width' })
end
if config.mappings.move_with_alt then
-- Move only sideways in command mode. Using `silent = false` makes movements
-- to be immediately shown.
map('c', '<M-h>', '<Left>', { silent = false, desc = 'Left' })
map('c', '<M-l>', '<Right>', { silent = false, desc = 'Right' })
-- Don't `noremap` in insert mode to have these keybindings behave exactly
-- like arrows (crucial inside TelescopePrompt)
map('i', '<M-h>', '<Left>', { noremap = false, desc = 'Left' })
map('i', '<M-j>', '<Down>', { noremap = false, desc = 'Down' })
map('i', '<M-k>', '<Up>', { noremap = false, desc = 'Up' })
map('i', '<M-l>', '<Right>', { noremap = false, desc = 'Right' })
map('t', '<M-h>', '<Left>', { desc = 'Left' })
map('t', '<M-j>', '<Down>', { desc = 'Down' })
map('t', '<M-k>', '<Up>', { desc = 'Up' })
map('t', '<M-l>', '<Right>', { desc = 'Right' })
end
end
H.keymap_set = function(modes, lhs, rhs, opts)
-- NOTE: Use `<C-H>`, `<C-Up>`, `<M-h>` casing (instead of `<C-h>`, `<C-up>`,
-- `<M-H>`) to match the `lhs` of keymap info. Otherwise it will say that
-- mapping doesn't exist when in fact it does.
if type(modes) == 'string' then modes = { modes } end
for _, mode in ipairs(modes) do
-- Don't map if mapping is already set **globally**
local map_info = H.get_map_info(mode, lhs)
if not H.is_default_keymap(mode, lhs, map_info) then return end
-- Map
H.map(mode, lhs, rhs, opts)
end
end
H.is_default_keymap = function(mode, lhs, map_info)
if map_info == nil then return true end
local rhs, desc = map_info.rhs or '', map_info.desc or ''
-- Some mappings are set by default in Neovim
if mode == 'n' and lhs == '<C-L>' then return rhs:find('nohl') ~= nil end
if mode == 'i' and lhs == '<C-S>' then return desc:find('signature') ~= nil end
if mode == 'x' and lhs == '*' then return rhs == [[y/\V<C-R>"<CR>]] end
if mode == 'x' and lhs == '#' then return rhs == [[y?\V<C-R>"<CR>]] end
end
H.get_map_info = function(mode, lhs)
local keymaps = vim.api.nvim_get_keymap(mode)
for _, info in ipairs(keymaps) do
if info.lhs == lhs then return info end
end
end
-- Autocommands ---------------------------------------------------------------
H.apply_autocommands = function(config)
local augroup = vim.api.nvim_create_augroup('MiniBasicsAutocommands', {})
local au = function(event, pattern, callback, desc)
vim.api.nvim_create_autocmd(event, { group = augroup, pattern = pattern, callback = callback, desc = desc })
end
if config.autocommands.basic then
au('TextYankPost', '*', function() vim.highlight.on_yank() end, 'Highlight yanked text')
local start_terminal_insert = vim.schedule_wrap(function(data)
-- Try to start terminal mode only if target terminal is current
if not (vim.api.nvim_get_current_buf() == data.buf and vim.bo.buftype == 'terminal') then return end
vim.cmd('startinsert')
end)
au('TermOpen', 'term://*', start_terminal_insert, 'Start builtin terminal in Insert mode')
end
if config.autocommands.relnum_in_visual_mode then
au(
'ModeChanged',
-- Show relative numbers only when they matter (linewise and blockwise
-- selection) and 'number' is set (avoids horizontal flickering)
'*:[V\x16]*',
function() vim.wo.relativenumber = vim.wo.number end,
'Show relative line numbers'
)
au(
'ModeChanged',
'[V\x16]*:*',
-- Hide relative numbers when neither linewise/blockwise mode is on
function() vim.wo.relativenumber = string.find(vim.fn.mode(), '^[V\22]') ~= nil end,
'Hide relative line numbers'
)
end
end
-- Utilities ------------------------------------------------------------------
H.map = function(mode, lhs, rhs, opts)
if lhs == '' then return end
opts = vim.tbl_deep_extend('force', { silent = true }, opts or {})
vim.keymap.set(mode, lhs, rhs, opts)
end
H.diagnostic_is_enabled = function(buf_id)
local res = H.buffer_diagnostic_state[buf_id]
if res == nil then res = true end
return res
end
if vim.fn.has('nvim-0.9') == 1 then
H.diagnostic_is_enabled = function(buf_id) return not vim.diagnostic.is_disabled(buf_id) end
end
return MiniBasics