-
Notifications
You must be signed in to change notification settings - Fork 0
/
init.util.vim
229 lines (215 loc) · 9.3 KB
/
init.util.vim
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
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
""" Convenience functions {{{1
" Checks if given string starts with given substring
function! StartsWith(longer, shorter) abort
return a:longer[0:len(a:shorter)-1] ==# a:shorter
endfunction
" US qwerty keyboard
let s:lower_key = {
\ '!': '1',
\ '@': '2',
\ '#': '3',
\ '$': '4',
\ '%': '5',
\ '^': '6',
\ '&': '7',
\ '*': '8',
\ '(': '9',
\ ')': '0',
\ '_': '-',
\ '+': '=',
\ '{': '[',
\ '}': ']',
\ '|': '\\',
\ ':': ';',
\ '"': "'",
\ '<': ',',
\ '>': '.',
\ '?': '/',
\}
" Neovim, unlike iTerm, requires shifting for neovim
function s:ShiftModifierIfNeededForMetaInNeovimInKitty(key)
return has_key(s:lower_key, a:key) ? 'S-' : ''
endfunction
function s:ShiftModifierIfNeededForSuper(key)
let l:key = substitute(a:key, '.*-', '', '')
return strlen(l:key) == 1 && l:key >=# 'A' && l:key <=# 'Z' ||
\ has_key(s:lower_key, l:key) ? 'S-' : ''
endfunction
function s:ShiftModifierIfNeededForControl(key)
let l:key = substitute(a:key, '.*-', '', '')
return strlen(l:key) == 1 && l:key >=# 'A' && l:key <=# 'Z' ? 'S-' : ''
endfunction
function s:LowerKey(key)
if exists('g:kitty_term') && has_key(s:lower_key, a:key)
return s:lower_key[a:key]
endif
return tolower(a:key)
endfunction
function! s:IsInsertLikeMode(mode) abort
return index(['map!', 'noremap!', 'imap', 'inoremap', 'cmap', 'cnoremap', 'lmap', 'lnoremap'], a:mode) >= 0
endfunction
" Returns a RHS prefix for given mode
function! s:RhsPrefixForMode(rhs, mode) abort
if !StartsWith(a:rhs, '<Cmd>')
if a:mode == 'tmap' || a:mode == 'tnoremap'
return '<C-\><C-N>'
elseif s:IsInsertLikeMode(a:mode)
return '<C-o>'
endif
endif
return ''
endfunction
" Returns a list of RHS prefixes for insert-like and terminal mode, if
" necessary
function! s:RhsPrefixesForAllModes(rhs) abort
if !StartsWith(a:rhs, '<Cmd>')
return ['<C-o>', '<C-\><C-N>']
endif
return ['', '']
endfunction
" There are some cases where using <Esc>x is necessary over <M-x>
" - for MacVim GUI, unless `macmeta` is on: https://github.com/macvim-dev/macvim/issues/1321
" - for iTerm vim, unless this is set:
" xterm control sequence can enable modifyOtherKeys mode"
"
" NOTE:
" - in iTerm, it's preferable to have modifyOtherKeys on, because that way,
" keymaps will work in insert-like modes too (if modifyOtherKeys is off, then
" <Esc>x keymaps would conflict with the <Esc> used to exit insert mode; also
" vmaps <Esc><Up> interfere for when you're trying to exit out of visual mode
" and quickly use an arrow key)
" - in MacVim GUI, it is preferable to not have `macmeta` for the same reason
" as preferring iTerm to have modifyOtherKeys set to on.
" XXX 2022-11-13 I'm not sure if this is true; maybe it's because of the way I coded it; but I
" don't want to try to fix it and go through another full round of testing.
function! g:NormalizeMetaModifier(str) abort
if exists('g:nvim') && exists('g:kitty_term')
" FIXME: this only works for a single <M-key> sequence, not multiple
let l:key = substitute(a:str, '^<M-\(.\)>$', '\1', '')
if l:key != a:str
return '<M-' . <SID>ShiftModifierIfNeededForMetaInNeovimInKitty(l:key) . <SID>LowerKey(l:key) . '>'
else
return a:str
endif
elseif exists('g:nvim') || (exists('g:vim') && (exists('g:gui_macvim') && has('macmeta') || exists('g:tui_vim') && g:use_extended_keys_in_terminal))
return a:str
endif
let l:str = substitute(a:str, '<M->>', '<Esc><gt>', 'g')
" We don't touch keys with multiple modififers
let l:str = substitute(l:str, '<M-\(.[^->]\+\)>', '<Esc><\1>', 'g')
return substitute(l:str, '<M-\(.\)>', '<Esc>\1', 'g')
endfunction
" Allows mapping aliases for characters like `å` to `<M-a>`
function! MapAlias(keys, rhs, modes = 'all', no_insert = v:false) abort
let l:modes = a:modes
let l:rhs = g:NormalizeMetaModifier(a:rhs)
if type(l:modes) == type("") && l:modes == 'all'
execute 'map' a:keys l:rhs
" Exclude insert mode because we don't want the <Esc> to slow down exiting insert mode
if !a:no_insert && !StartsWith(a:keys, '<Esc>') && !StartsWith(a:keys, '')
execute 'map!' a:keys l:rhs
endif
execute 'tmap' a:keys l:rhs
else
for mode in l:modes
if !a:no_insert || !s:IsInsertLikeMode(mode)
execute mode a:keys l:rhs
endif
endfor
endif
endfunction
" Maps the key sequence to RHS, optionally with specific modes
" - modes: 'all' implies: map, imap, tmap (so no smap, cmap or lmap)
" - no_insert: filters modes to exclude insert-like modes. See s:IsInsertLikeMode
" - remap: only applies to modes='all'
function! MapKey(keys, rhs, modes = "all", no_insert = v:false, remap = v:false, map_flag = '') abort
let l:keys = a:keys
let l:rhs = a:rhs
let l:modes = a:modes
" 1) If nvim, then we don't change anything
" 2) If MacVim GUI, then we normalize and try again
" 3) If vim TUI, then we do two mappings: one normalized and one
" unchanged. That's because we don't know if iTerm has modifyOtherKeys
" on or not.
if exists('g:vim')
let l:lhs_normalized = g:NormalizeMetaModifier(a:keys)
let l:rhs_normalized = g:NormalizeMetaModifier(a:rhs)
if l:lhs_normalized != a:keys || l:rhs_normalized != a:rhs
" If we're automatically normalizing to <Esc>, we don't want to
" map any insert-like mode because we don't want conflict with
" <Esc> key to get out of normal mode.
let l:no_insert = l:lhs_normalized != a:keys || a:no_insert
call MapKey(l:lhs_normalized, l:rhs_normalized,
\ l:modes, l:no_insert, a:remap, a:map_flag)
if exists('g:gui_running')
return
endif
endif
endif
let l:nore = a:remap ? '' : 'nore'
let l:prefixes = s:RhsPrefixesForAllModes(l:rhs)
if type(l:modes) == type("") && l:modes == 'all'
execute l:nore . 'map' a:map_flag l:keys l:rhs
execute 't' . l:nore . 'map' a:map_flag l:keys l:prefixes[1] . l:rhs
if !a:no_insert
execute 'i' . l:nore . 'map' a:map_flag l:keys l:prefixes[0] . l:rhs
endif
else
for mode in l:modes
if !a:no_insert || !s:IsInsertLikeMode(mode)
execute mode a:map_flag l:keys s:RhsPrefixForMode(l:rhs, mode) . l:rhs
endif
endfor
endif
endfunction
" Maps the Command (⌘) key, or in TUIs the fallback Option (⌥), key.
" That's because terminals like iTerm swallow up key bindings with the ⌘ key.
" NOTE: in some cases, `key` is allowed to contain a modifier, but not `M-`
" in which case, use MapSuperOrControlKey()
function! MapSuperKey(key, rhs, modes = "all", no_insert = v:false, remap = v:false, map_flag = '') abort
let l:no_insert = a:no_insert
if exists('g:gui_running')
if exists('g:nvim')
let l:key = <SID>ShiftModifierIfNeededForSuper(a:key)
let l:key .= l:key != '' ? tolower(a:key) : a:key
else
let l:key = a:key
endif
let l:keys = '<D-' . l:key . '>'
else
let l:keys = g:NormalizeMetaModifier('<M-' . a:key . '>')
let l:no_insert = l:keys != '<M-' . a:key . '>' || l:no_insert
endif
call MapKey(l:keys, a:rhs, a:modes, l:no_insert, a:remap, a:map_flag)
endfunction
" Maps the Control (⌃) key, or in GUIs the fallback Option (⌥), key.
" 2022-10-30 That's because neither MacVim/VimR support modifyOtherKeys yet
" and thus treat <C-S-A> like <C-A>.
" NOTE: in some cases, `key` is allowed to contain a modifier, but not `M-`,
" in which case, use MapSuperOrControlKey()
function! MapControlKey(key, rhs, modes = "all", no_insert = v:false, remap = v:false, map_flag = '') abort
let l:no_insert = a:no_insert
if exists('g:gui_running')
let l:keys = g:NormalizeMetaModifier('<M-' . a:key . '>')
let l:no_insert = l:keys != '<M-' . a:key . '>' || l:no_insert
else
let l:key = <SID>ShiftModifierIfNeededForControl(a:key)
" XXX I think the tolower isn't needed here because case doesn't matter with <C->
let l:key .= l:key != '' ? tolower(a:key) : a:key
let l:keys = '<C-' . l:key . '>'
endif
call MapKey(l:keys, a:rhs, a:modes, l:no_insert, a:remap, a:map_flag)
endfunction
" Maps the Control (⌃) key in TUIs or the Command (⌘) key in GUIs.
" 2022-10-30 That's because neither MacVim/VimR support modifyOtherKeys yet
" and thus treat <C-D-a> like <C-A>.
function! MapSuperOrControlKey(key, rhs, modes = "all", no_insert = v:false, remap = v:false, map_flag = '') abort
if exists('g:gui_running')
call MapKey('<D-' . <SID>ShiftModifierIfNeededForSuper(a:key) . <SID>LowerKey(a:key) . '>',
\ a:rhs, a:modes, a:no_insert, a:remap, a:map_flag)
else
call MapKey('<C-' . <SID>ShiftModifierIfNeededForControl(a:key) . a:key . '>',
\ a:rhs, a:modes, a:no_insert, a:remap, a:map_flag)
endif
endfunction