I act very rarely on the whole file but quite often on hunks, so it makes sense to have the mappings for the latter shorter. The new mappings I wanted to use for the whole file are conflicting with `<leader>gf` (i.e. `:GFiles`), so I've disabled them for now.
546 lines
18 KiB
VimL
546 lines
18 KiB
VimL
" vim: set foldmethod=marker:
|
|
" Keybindings """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
|
" Clear search result highlights when pressing Escape in normal mode
|
|
nnoremap <silent> <Esc> :nohlsearch<CR><Esc>
|
|
|
|
" Indentation jump
|
|
" https://vim.fandom.com/wiki/Move_to_next/previous_line_with_same_indentation
|
|
" noremap <silent> <C-k> :call search('^'. matchstr(getline('.'), '\(^\s*\)') .'\%<' . line('.') . 'l\S', 'be')<CR>
|
|
" noremap <silent> <C-j> :call search('^'. matchstr(getline('.'), '\(^\s*\)') .'\%>' . line('.') . 'l\S', 'e')<CR>
|
|
|
|
" Split view navigation
|
|
" Create new panes
|
|
nnoremap <C-w>N <Cmd>vsplit<CR>
|
|
nnoremap <C-w>n <Cmd>split<CR>
|
|
|
|
" My brain expects <C-w>{gf,gF} to open in a split, not a tab.
|
|
" Swap the mappings for tab and split.
|
|
nnoremap <C-w>gf <C-w>f
|
|
nnoremap <C-w>gF <C-w>F
|
|
nnoremap <C-w>f <C-w>gf
|
|
nnoremap <C-w><C-f> <C-w>gf
|
|
nnoremap <C-w>F <C-w>gF
|
|
|
|
" Analogous mapping to tmux's <prefix>! to move the current window to a new tab
|
|
nnoremap <C-w>! <C-w>T
|
|
|
|
" Substitute command
|
|
if (exists('+inccommand') && &inccommand != '')
|
|
nnoremap S :%s/
|
|
vnoremap S :s/\%V
|
|
else
|
|
" This does not work with live previewing commands (inccommand) since the
|
|
" replace pattern is already defined and thus everything matching the search
|
|
" pattern is just deleted.
|
|
nnoremap S :%s//gc<Left><Left><Left>
|
|
vnoremap S :s/\%V/gc<Left><Left><Left>
|
|
endif
|
|
|
|
" Interact with the system clipboard
|
|
if (has('clipboard'))
|
|
map <leader>y "+y
|
|
map <leader>Y "+Y
|
|
map <leader>p "+p
|
|
map <leader>P "+P
|
|
endif
|
|
|
|
" Do not move the cursor to the start of the selection after a yank
|
|
" https://stackoverflow.com/a/3806664/20927629
|
|
vmap y ygv<Esc>
|
|
|
|
" Ctrl-Backspace should delete words in insert mode and on command-line.
|
|
noremap! <C-BS> <C-W>
|
|
map! <C-H> <C-BS>
|
|
|
|
" Correct word with best/first suggestion.
|
|
noremap <leader>c 1z=
|
|
" Correct next or last misspelled word (and their non-rare/region versions)
|
|
" without moving
|
|
" TODO: see :keepjumps
|
|
" Problem: with keepjumps the <C-O> is not possible anymore
|
|
noremap <leader>]s ]s1z=<C-O>
|
|
noremap <leader>[s [s1z=<C-O>
|
|
noremap <leader>]S ]S1z=<C-O>
|
|
noremap <leader>[S [S1z=<C-O>
|
|
|
|
" Toggle spell language between German and English
|
|
function! CycleSpellLang()
|
|
if (&spelllang == 'en')
|
|
setl spelllang=de
|
|
elseif (&spelllang == 'de')
|
|
setl spelllang=en
|
|
endif
|
|
endfunction
|
|
" Toggle spell, cycle and set spelllang
|
|
map <leader>st <Cmd>set spell!<CR>
|
|
map <leader>sc <Cmd>call CycleSpellLang()<CR>
|
|
map <leader>ss :set spelllang=
|
|
|
|
" Jump through jump table but center while still respecting 'foldopen'
|
|
noremap <expr> <C-I> '<C-I>' . (match(&fdo, 'mark') > -1 ? 'zv' : '') . 'zz'
|
|
noremap <expr> <C-O> '<C-O>' . (match(&fdo, 'mark') > -1 ? 'zv' : '') . 'zz'
|
|
nmap <Tab> <C-I>
|
|
nmap <S-Tab> <C-O>
|
|
|
|
" Terminal
|
|
if (has('nvim'))
|
|
tnoremap <leader><Esc> <C-\><C-n>
|
|
nmap <leader><CR> :split +terminal<CR>i
|
|
nmap <leader>v<CR> :vsplit +terminal<CR>i
|
|
elseif (has('terminal'))
|
|
nmap <leader><CR> <Cmd>terminal<CR>
|
|
endif
|
|
|
|
" Plugin specific bindings
|
|
if (get(g:, 'loaded_fzf'))
|
|
nmap <leader>f <Cmd>Files<CR>
|
|
nmap <leader>j <Cmd>Lines<CR>
|
|
nmap <leader>/ <Cmd>Lines<CR>
|
|
nmap <leader>h <Cmd>Helptags<CR>
|
|
" TODO: fix this?
|
|
if (get(g:, 'loaded_gutentags') || 1)
|
|
nmap <leader>t <Cmd>Tags<CR>
|
|
nmap <leader>bt <Cmd>BTags<CR>
|
|
endif
|
|
endif
|
|
|
|
" Search for selected text.
|
|
" Modified from https://vim.fandom.com/wiki/Search_for_visually_selected_text
|
|
function! GetVisualSelection()
|
|
let l:old_reg = getreg('"')
|
|
let l:old_regtype = getregtype('"')
|
|
norm gvy
|
|
let l:sel = getreg('"')
|
|
call setreg('"', l:old_reg, l:old_regtype)
|
|
return l:sel
|
|
endfunction
|
|
|
|
vmap * /\V<C-R>=escape(GetVisualSelection(),'/\')<CR><CR>
|
|
vmap # ?\V<C-R>=escape(GetVisualSelection(),'?\')<CR><CR>
|
|
|
|
" Extended `*`. Starts vim search (without jump) and ripgrep
|
|
nmap <leader>* :let @/ = '\<' . expand('<cword>') . '\>' <bar>
|
|
\ set hlsearch <bar>
|
|
\ Rg \b<C-R>=expand('<cword>')<CR>\b<CR>
|
|
vmap <leader>* :<C-U>let @/ = "\\V<C-R>=escape(escape(GetVisualSelection(), '\'), '"\')<CR>" <bar>
|
|
\ set hlsearch <bar>
|
|
\ Rg <C-R>=escape(GetVisualSelection(), '.\[]<bar>*+?{}^$()')<CR><CR>
|
|
nmap <leader>g* :let @/ = expand('<cword>') <bar>
|
|
\ set hlsearch <bar>
|
|
\ Rg <C-R>=expand('<cword>')<CR><CR>
|
|
vmap <leader>g* <leader>*
|
|
|
|
" Search inside visual selection
|
|
noremap <leader>v/ /\%V
|
|
vmap <leader>/ <Esc><leader>v/
|
|
|
|
" Select last pasted text in same visual mode as it was selected (v, V, or ^V)
|
|
" Taken from: https://vim.fandom.com/wiki/Selecting_your_pasted_text
|
|
nnoremap <expr> gp '`[' . strpart(getregtype(), 0, 1) . '`]'
|
|
|
|
" Git bindings
|
|
|
|
" Reference the commit that the cursor is currently on with the 'reference'
|
|
" format (Mnemonic: "git reference commit").
|
|
" NOTE: I can't use --pretty=reference since it would insert the abbreviated
|
|
" hash additionally to the existing hash.
|
|
" NOTE: This uses `system` and not `:r!` to insert the text directly at the
|
|
" cursor. `subject[:-1]` cuts off the trailing newline.
|
|
" TODO: print error message but insert nothing on git error
|
|
nmap <leader>grc :let subject=system('git show -s --date=short --pretty="format:(%s, %ad)" <C-R><C-W>')<CR>viw<Esc>a <C-R>=subject[:-1]<CR><Esc>
|
|
|
|
" Insert a Signed-off-by trailer
|
|
nmap <leader>gso :r!git config --get user.name<CR>:r!git config --get user.email<CR>I<<ESC>A><ESC>kJISigned-off-by: <ESC>
|
|
|
|
" Add, stash or checkout the current file
|
|
" TODO: Conflict with <leader>gf
|
|
"nmap <leader>gfa <Cmd>!git add -- %<CR>
|
|
"nmap <leader>gfs <Cmd>!git stash -- %<CR>
|
|
"nmap <leader>gfu <Cmd>!git checkout -- %<CR>
|
|
|
|
if exists('g:loaded_fugitive')
|
|
" Interactive `git status`
|
|
nmap <leader>gg <Cmd>G<CR>
|
|
" Start a commit and open the message in a split
|
|
nmap <leader>gcc <Cmd>G commit<CR>
|
|
" Amend the current commit and open the message in a split
|
|
nmap <leader>gca <Cmd>G commit --amend<CR>
|
|
" Move to root of directory
|
|
nmap <leader>gcd <Cmd>Gcd<CR>
|
|
" git blame in scroll bound vertical split (only the commit hashes, see
|
|
" :help :Git_blame)
|
|
nmap <leader>gb :G blame<CR>C
|
|
else
|
|
" Move to root of repository
|
|
" NOTE: only works if a file is already opened
|
|
nnoremap <leader>gcd <Cmd>cd %:h <Bar> cd `git rev-parse --show-toplevel`<CR>
|
|
endif
|
|
|
|
if exists('g:loaded_gitgutter')
|
|
" Mnemonic: "git <add|undo|preview>"
|
|
nmap <leader>ga <Plug>(GitGutterStageHunk)
|
|
xmap <leader>ga <Plug>(GitGutterStageHunk)
|
|
" TODO: nmap <leader>gs <Plug>(GitGutterStashHunk)
|
|
nmap <leader>gu <Plug>(GitGutterUndoHunk)
|
|
nmap <leader>gp <Plug>(GitGutterPreviewHunk)
|
|
|
|
" Add hunk/h version to textobject bindings that use `c` (for `change I
|
|
" presume?) (e.g. ic -> ih)
|
|
omap ih <Plug>(GitGutterTextObjectInnerPending)
|
|
omap ah <Plug>(GitGutterTextObjectOuterPending)
|
|
xmap ih <Plug>(GitGutterTextObjectInnerVisual)
|
|
xmap ah <Plug>(GitGutterTextObjectOuterVisual)
|
|
" Same for hunk navigation bindings + center line
|
|
nmap [h <Plug>(GitGutterPrevHunk)zz
|
|
nmap ]h <Plug>(GitGutterNextHunk)zz
|
|
endif
|
|
|
|
if (get(g:, 'loaded_fzf'))
|
|
" git files that `git status` lists
|
|
nmap <leader>gf <Cmd>GFiles?<CR>
|
|
" 'git log (log?)' and 'git log buffer '
|
|
map <leader>gll <Cmd>Commits<CR>
|
|
map <leader>glb <Cmd>BCommits<CR>
|
|
" TODO: <leader>glb should restrict the log to all staged files when called
|
|
" in .git/COMMIT_EDITMSG, maybe with an ftplugin?
|
|
endif
|
|
|
|
" Y should behave like D & C does
|
|
nnoremap Y y$
|
|
|
|
" Clear line (`cc` but stay in normal mode)
|
|
nmap <leader>dd 0D
|
|
vmap <leader>d <Cmd>keepp '<,'>s/^.*$//<CR>
|
|
|
|
" Fix & command to also use last flags
|
|
nnoremap & <Cmd>&&<CR>
|
|
xnoremap & <Cmd>&&<CR>
|
|
|
|
" see :help i_ctrl-g_u
|
|
" Do not break undo sequence when using the arrow keys or home and end in insert
|
|
" mode
|
|
inoremap <Left> <C-G>U<Left>
|
|
inoremap <Right> <C-G>U<Right>
|
|
inoremap <expr> <Home> col('.') == match(getline('.'), '\S') + 1 ?
|
|
\ repeat('<C-G>U<Left>', col('.') - 1) :
|
|
\ (col('.') < match(getline('.'), '\S') ?
|
|
\ repeat('<C-G>U<Right>', match(getline('.'), '\S') + 0) :
|
|
\ repeat('<C-G>U<Left>', col('.') - 1 - match(getline('.'), '\S')))
|
|
inoremap <expr> <End> repeat('<C-G>U<Right>', col('$') - col('.'))
|
|
" break undo sequence with every space and newline, making insert mode changes
|
|
" revertible in smaller chunks
|
|
inoremap <Space> <C-G>u<Space>
|
|
|
|
" Open the manpage in the WORD under cursor
|
|
nnoremap gm :Man <C-r><C-a><CR>
|
|
xnoremap gm :Man <C-r><C-a><CR>
|
|
|
|
" Format the current paragraph, while keeping the cursor position
|
|
nmap Q gwap
|
|
imap <C-q> <C-o>Q
|
|
|
|
" Swap movement mappings that act on display lines with the normal ones, making
|
|
" it easier to navigate long wrapped lines.
|
|
function! MapWrapMovement()
|
|
let l:mappings = {
|
|
\ 'j': 'gj',
|
|
\ 'k': 'gk',
|
|
\ '0': 'g0',
|
|
\ '^': 'g^',
|
|
\ '$': 'g$',
|
|
\ 'gj': 'j',
|
|
\ 'gk': 'k',
|
|
\ 'g0': '0',
|
|
\ 'g^': '^',
|
|
\ 'g$': '$',
|
|
\ }
|
|
if &wrap
|
|
for [l:from, l:to] in items(l:mappings)
|
|
execute 'noremap ' .. l:from .. ' ' .. l:to
|
|
endfor
|
|
else
|
|
for l:key in keys(l:mappings)
|
|
execute 'silent! unmap ' .. l:key
|
|
endfor
|
|
endif
|
|
endfunction
|
|
augroup WrapMovementMappings
|
|
au!
|
|
au OptionSet wrap call MapWrapMovement()
|
|
augroup END
|
|
|
|
" Convert Unix timestamp to human readable
|
|
" Mnemonic: "Unix timestamp convert" with pun to UTC
|
|
nnoremap <leader>utc ciw<C-r>=strftime("%F %T", @")<CR><Esc>
|
|
vnoremap <leader>utc <Cmd>keepp '<,'>s/\v(^\|[^0-9])\zs[0-9]{10}\ze([^0-9]\|$)/\=strftime("%F %T",submatch(0))/g<CR>
|
|
|
|
" Convert decimal numbers to hex
|
|
" https://stackoverflow.com/a/1118642
|
|
nnoremap <leader>hex ciw<C-r>=printf("0x%X", @")<CR><Esc>
|
|
vnoremap <leader>hex <Cmd>keepp '<,'>s/\v<\d+>/\=printf("0x%X", submatch(0))/g<CR>
|
|
|
|
" TODO: <leader>sec that uses the `duration` alias from zsh
|
|
|
|
" Relax mappings that jump to opening braces on first column: Just make sure
|
|
" they are on an unindented line. This is useful for files that use a different
|
|
" coding style guide than the kernel and similar.
|
|
" TODO: support [count]
|
|
" TODO: sections? (see :h [[ and :h section)
|
|
" TODO: exclusive and exclusive-linewise?
|
|
noremap <silent> [[ <Cmd>call search('\v^(\S.*)?\{', 'besW')<CR>
|
|
noremap <silent> ]] <Cmd>call search('\v^(\S.*)?\{', 'esW')<CR>
|
|
|
|
" Make `dest` do the same thing as `src` currently does. `src` can be remapped
|
|
" afterwards without `dest`'s behaviour changing. Call with `expand("<sflnum>")`
|
|
" as `lnum`.
|
|
function! s:mapcpy(to, from, lnum, mode = "")
|
|
let l:curr_map = maparg(a:from, a:mode, 0, 1)
|
|
|
|
if empty(l:curr_map)
|
|
" Simply do a `*noremap <to> <from>` (but with correct lnum)
|
|
let l:curr_map = {
|
|
\ "rhs": a:from, "noremap": 1, "mode": a:mode,
|
|
\ "sid": expand("<SID>"),
|
|
\ "script": 0, "expr": 0, "buffer": 0, "silent": 0,
|
|
\ "nowait": 0, "abbr": 0, "scriptversion": 1,
|
|
\ }
|
|
endif
|
|
|
|
" Overwrite lhs of current mapping. Also change lnum to calling line.
|
|
let l:curr_map["lnum"] = a:lnum
|
|
let l:curr_map["lhs"] = a:to
|
|
if has("nvim")
|
|
" TODO: is `1, 1, 1` correct?
|
|
let l:curr_map["lhsraw"] = nvim_replace_termcodes(a:to, 1, 1, 1)
|
|
else
|
|
" TODO: convert to bytes (find inverse of keytrans())
|
|
let l:curr_map["lhsraw"] = a:to
|
|
endif
|
|
if has_key(l:curr_map, "lhsrawalt")
|
|
call remove(l:curr_map, "lhsrawalt")
|
|
endif
|
|
|
|
call mapset(l:curr_map)
|
|
endfunction
|
|
|
|
" Swap ]] and ][, so that ]] jumps forward to the next '}' and ][ to '{'. I find
|
|
" this more intuitive, as now the first bracket indicates the jump direction
|
|
" (i.e. ] -> forward, [ -> backward) and the second bracket the orientation of
|
|
" the target brace (i.e. [ -> {, ] -> }) .
|
|
"
|
|
" While doing this, keep the functionality from above that the opening brace
|
|
" does not need to be in the first column. I could have simply mapped ][ above
|
|
" instead of ]], but prefer to have it modular. In the case that ]] is not
|
|
" mapped yet (e.g. because I disabled the mapping above), this simply does a
|
|
" `noremap ][ ]]`.
|
|
call s:mapcpy("][", "]]", expand("<sflnum>"))
|
|
noremap ]] ][
|
|
|
|
" Strip trailing whitespace
|
|
nnoremap <leader><space> <Cmd>keepp silent! %s/\v\s+$//<CR>
|
|
vnoremap <leader><space> <Cmd>keepp silent! '<,'>s/\v\s+$//<CR>
|
|
|
|
" Convert double quotes to single. Convert only pairs to lower the false
|
|
" positive rate.
|
|
nnoremap <leader>" <Cmd>keepp silent! %s/\v"([^"]*)"/'\1'/g<CR>
|
|
vnoremap <leader>" <Cmd>keepp silent! '<,'>s/\v"([^"]*)"/'\1'/g<CR>
|
|
|
|
" Better™ >> and <<. When using tabs for indentation and spaces for alignment,
|
|
" vim's behaviour is pretty disappointing since it will convert the indentation
|
|
" to a series of tabs followed by spaces. 'preserveindent' changes that, but
|
|
" adds the tabs at the end of the indentation, which does not make sense for
|
|
" aligned text as it will then have indentation consisting of tabs, spaces and
|
|
" again tabs.
|
|
"
|
|
" This tries to improve this by overriding >> and << to simply add or remove a
|
|
" tab (or spaces if 'expandtab' is set) at the beginning of the line. It also
|
|
" keeps the cursor on the same character and uses the normal-mode count like the
|
|
" visual one instead of targeting multiple lines since I always use visual mode
|
|
" for that (and it makes the function usable from normal and visual mode).
|
|
function! s:indent(count, left, visual)
|
|
" NOTE: the \t should be unescaped/in double quotes for strdisplaywidth
|
|
let l:indentation = &expandtab ? repeat(' ', shiftwidth()) : "\t"
|
|
" Count changes the level not the number of lines
|
|
let l:indentation = repeat(l:indentation, a:count + 1)
|
|
|
|
" save current cursor position
|
|
let l:line = line('.')
|
|
let l:col = virtcol('.')
|
|
let l:line_len = len(getline(l:line))
|
|
|
|
let l:off = strdisplaywidth(l:indentation)
|
|
if !a:left
|
|
let l:col += l:off
|
|
let l:substitute = 's/^/' .. l:indentation .. '/'
|
|
else
|
|
let l:col -= l:off
|
|
let l:substitute = 's/^' .. l:indentation .. '//'
|
|
endif
|
|
let l:substitute = (a:visual ? "'<,'>" : '') .. l:substitute
|
|
|
|
" Remove or add indentation at the beginning of the line
|
|
execute l:substitute
|
|
|
|
" Reset cursor position
|
|
if a:visual
|
|
" the cursor jumps to the last line that changed - reset it
|
|
execute ':' .. l:line
|
|
endif
|
|
if l:line_len != len(getline(l:line))
|
|
" Only change column if the current line changed
|
|
execute 'normal' l:col .. '|'
|
|
endif
|
|
endfunction
|
|
|
|
nmap >> <Cmd>call <SID>indent(v:count, v:false, v:false)<CR>
|
|
nmap << <Cmd>call <SID>indent(v:count, v:true, v:false)<CR>
|
|
" Also keep(s) selection after changing the indentation in visual mode
|
|
vmap > <Cmd>call <SID>indent(v:count, v:false, v:true)<CR>
|
|
vmap < <Cmd>call <SID>indent(v:count, v:true, v:true)<CR>
|
|
vnoremap = =gv
|
|
|
|
" Center search results while still respecting 'foldopen'
|
|
function! s:CenterNext(count, command)
|
|
let l:foldopen = match(&foldopen, 'search') > -1 ? 'zv' : ''
|
|
|
|
" Search count (i.e. [5/10]) will not display with 'lazyredraw'
|
|
let l:lazyredraw_bkp = &lazyredraw
|
|
set nolazyredraw
|
|
|
|
execute 'normal! ' .. a:count .. a:command .. l:foldopen .. 'zz'
|
|
|
|
let &lazyredraw = l:lazyredraw_bkp
|
|
endfunction
|
|
" NOTE: v:hlsearch's value is restored when returning from a function and thus
|
|
" needs to be set here (see :h function-search-undo)
|
|
map n <Cmd>call <SID>CenterNext(v:count1, 'n') <Bar> let v:hlsearch = 1<CR>
|
|
map N <Cmd>call <SID>CenterNext(v:count1, 'N') <Bar> let v:hlsearch = 1<CR>
|
|
|
|
cnoremap <expr> <CR> "<CR>" .
|
|
\ (getcmdtype() == '/' \|\| getcmdtype() == '?'
|
|
\ ? (match(&fdo, 'search') > -1 ? 'zv' : '') . "zz"
|
|
\ : "")
|
|
|
|
" Switch to lower/upper case
|
|
nnoremap <leader><C-U> gUl
|
|
vnoremap <leader><C-U> gU
|
|
nnoremap <leader><C-L> gul
|
|
vnoremap <leader><C-L> gu
|
|
|
|
" Expand visual selection over all directly following lines in the given
|
|
" direction that contain the current selection at the same position.
|
|
"
|
|
" Example:
|
|
" ```
|
|
" - TODO: ...
|
|
" - TODO: ...
|
|
" - TODO: ...
|
|
" ```
|
|
"
|
|
" In visual block one can select `TODO: ` on the first line and then call
|
|
" `ExpandVisualSelection(1)` which results in a block selection that spans over
|
|
" all other TODOs as well.
|
|
function! ExpandVisualSelection(direction)
|
|
let l:sel = escape(GetVisualSelection(), '\')
|
|
normal gv
|
|
|
|
" Move the cursor onto the side of the selection that points in the
|
|
" direction of the expansion.
|
|
let l:swap_ends = 0
|
|
if (
|
|
\ (getpos('.') == getpos("'>") && a:direction < 0) ||
|
|
\ (getpos('.') == getpos("'<") && a:direction > 0)
|
|
\)
|
|
normal o
|
|
let l:swap_ends = 1
|
|
endif
|
|
|
|
if (a:direction < 0)
|
|
" Because of the greedy nature of search(), we cannot use the same
|
|
" regex/approach as when searching forwards, as it will only ever match
|
|
" the preceding line.
|
|
while (
|
|
\ (line('.') - 1) &&
|
|
\ match(getline(line('.') - 1), '\%'.col("'<").'c'.l:sel) != -1
|
|
\)
|
|
call cursor(line('.') - 1, col("'<"))
|
|
endwhile
|
|
elseif (a:direction > 0)
|
|
let l:pat = '\v%#(.*\n.*%' . col("'<") . 'c\V' . l:sel . '\)\*'
|
|
call search(l:pat, 'e')
|
|
else
|
|
" TODO: expand in both directions
|
|
endif
|
|
|
|
" Reset cursor column
|
|
if (l:swap_ends && mode() == "\<C-V>")
|
|
normal O
|
|
endif
|
|
endfunction
|
|
|
|
vmap <silent> <leader>j <Cmd>call ExpandVisualSelection(1)<CR>
|
|
vmap <silent> <leader>k <Cmd>call ExpandVisualSelection(-1)<CR>
|
|
" TODO: Also map h and l that expand the visual selection over a range of lines
|
|
" as far as all the lines are identical
|
|
"
|
|
" In the above example with a visual block selection including only the
|
|
" dashes: <leader>l would now expand the selection to also include the
|
|
" `TODO: ` after (or more if `...` continues to be the same on all lines)
|
|
|
|
let g:macro_type_mappings = {
|
|
\ '<Space>': '_',
|
|
\ }
|
|
|
|
function! s:macro_type()
|
|
if !exists('s:macro_type')
|
|
let s:macro_type = 0
|
|
endif
|
|
|
|
if !s:macro_type
|
|
let s:macro_type = 1
|
|
|
|
" Disable on InsertLeave
|
|
au! macro_type InsertLeave * call s:macro_type()
|
|
|
|
for [l:from, l:to] in items(g:macro_type_mappings)
|
|
execute 'imap ' .. l:from .. ' ' .. l:to
|
|
endfor
|
|
|
|
for l:key in "abcdefghijklmnopqrstuvwxyz"
|
|
execute 'imap ' .. l:key .. ' ' .. toupper(l:key)
|
|
endfor
|
|
else
|
|
let s:macro_type = 0
|
|
au! macro_type
|
|
|
|
for l:key in keys(g:macro_type_mappings)
|
|
execute 'iunmap ' .. l:key
|
|
endfor
|
|
|
|
for l:key in "abcdefghijklmnopqrstuvwxyz"
|
|
execute 'iunmap ' .. l:key
|
|
endfor
|
|
endif
|
|
endfunction
|
|
|
|
" Type everything uppercase and underscores instead of spaces
|
|
noremap <leader>mac <Cmd>call <sid>macro_type()<CR>i
|
|
augroup macro_type
|
|
" NOTE: group used in macro_type()
|
|
au!
|
|
augroup END
|
|
|
|
" Escape underscores (useful when writing LaTeX)
|
|
vmap <leader>\_ <Cmd>keepp '<,'>s/\v(^<Bar>[^\\])\zs\ze_/\\/g<CR>
|
|
|
|
" Center line when opening line from quickfix window
|
|
augroup qf_centered_enter
|
|
au!
|
|
au FileType qf noremap <buffer> <CR> <CR>zz
|
|
augroup END
|
|
|
|
" TODO: make `gf` open absolute paths relative to PWD if possible
|
|
" TODO: operator pending mode: [ia]$ in shell scripts to select the current
|
|
" parameter expansion or command substitution
|
|
"
|