vim9script

# autocomplete with <c-n> and <c-p> when plugins are not available
def OnWhitespace(): bool
    return col('.') == 1 || getline('.')->strpart(0, col('.') - 1) =~ '\s$'
enddef
inoremap <expr> <Tab>   OnWhitespace() ? "\<tab>" : "\<c-n>"
inoremap <expr> <s-Tab> OnWhitespace() ? "\<s-tab>" : "\<c-p>"

# Y mapping, more natural but not vi compatible
map Y y$
# map gm to go to middle of line instead of middle of screen
nnoremap gm gM
# When softwrap happens move by screen line instead of file line
nnoremap j gj
nnoremap k gk
# Jump lines faster (use with H, M, L)
nnoremap <leader>j 5j
vnoremap <leader>j 5j
nnoremap <leader>k 5k
vnoremap <leader>k 5k
# g* selects foo in foobar while * selects <foo>, <> is word boundary. make * behave like g*
# nnoremap * g*
# nnoremap # g#
# Resize window using <ctrl> arrow keys
nnoremap <silent> <C-Up> :resize +2<cr>
nnoremap <silent> <C-Down> :resize -2<cr>
nnoremap <silent> <C-Right> :vertical resize -2<cr>
nnoremap <silent> <C-Left> :vertical resize +2<cr>

# alternative to 'packadd nohlsearch'
nnoremap <silent> <esc> :nohls<cr><esc>

# nnoremap <silent> <leader>h :bprevious<CR>
# nnoremap <silent> <leader>l :bnext<CR>

# Replace [[ ]] mappings that get redefined by ftplugin/vim.vim
# autocmd FileType * nnoremap <silent><buffer> [[ :bprevious<CR>
# autocmd FileType * nnoremap <silent><buffer> ]] :bnext<CR>
# Note:  ]" [" may hop comments (:verbose nmap ][)
#   See /opt/homebrew/Cellar/vim/9.0.1550/share/vim/vim90/ftplugin/vim.vim
# Buffer navigation
nnoremap <silent> [b :bprevious<CR>
nnoremap <silent> ]b :bnext<CR>
nnoremap <silent> [B :bfirst<CR>
nnoremap <silent> ]B :blast<CR>
# quickfix list
nnoremap <silent> [c :cprevious<CR>
nnoremap <silent> ]c :cnext<CR>
# nnoremap <silent> [C :cfirst<CR>
# nnoremap <silent> ]C :clast<CR>
nnoremap <silent> [C :colder<CR>
nnoremap <silent> ]C :cnewer<CR>
# location list (buffer local quickfix list)
nnoremap <silent> [l :lprevious<CR>
nnoremap <silent> ]l :lnext<CR>
nnoremap <silent> [L :lfirst<CR>
nnoremap <silent> ]L :llast<CR>
# file list -> load buffers using :args * :args **/*.js **/*.css
nnoremap <silent> [f :previous<CR>
nnoremap <silent> ]f :next<CR>
nnoremap <silent> [F :first<CR>
nnoremap <silent> ]F :last<CR>

# Map C-/ to do search within visually selected text
# (C-_ produces the same hex code as C-/)
vnoremap <C-_> <Esc>/\%V

# Emacs C-s C-w like solution: hightlight in visual mode and then type * or #
# `cgn` to replace text
# https://vonheikemen.github.io/devlog/tools/how-to-survive-without-multiple-cursors-in-vim/
xnoremap * :<c-u> call <SID>VSetSearch('/')<CR>/<C-R>=@/<CR><CR>
xnoremap # :<c-u> call <SID>VSetSearch('?')<CR>?<C-R>=@/<CR><CR>
# SID means script local function; 'call' is optional in vim9script.
def VSetSearch(cmdtype: string)
    var temp = getreg('s') # 's' is some register
    norm! gv"sy
    setreg('/', '\V' .. substitute(escape(@s, cmdtype .. '\'), '\n', '\\n', 'g'))
    setreg('s', temp) # restore whatever was in 's'
enddef

# NOTE: Use gp and gP for default purpose
# gp	Just like "p", but leave the cursor just after the new text.
# gP	Just like "P", but leave the cursor just after the new text.
# [p    To paste with correct indentation

# visually select recent pasted (or typed) text
#   remember `] takes you to end of pasted buffer, or use 'gp' to paste
nnoremap gs `[v`]

# Type %% on Vim’s command-line prompt, it expands to the path of the active buffer
# cnoremap <expr> %% getcmdtype() == ':' ? expand('%:h') .. '/' : '%%'

# <leader> mappings
nnoremap <leader>b <cmd>b#<cr>| # alternate buffer
nnoremap <leader>d <cmd>bw<cr>| # :bwipeout to purge, :bdelete still leaves buffer in unlisted state (:ls!)
nnoremap <leader>h <cmd>hide<cr>| # hide window
# nnoremap <leader>u <cmd>unhide<cr><c-w>w| # unhide = one window for each loaded buffer (splits horizontally, not useful)
tnoremap <c-w>h <c-w>:hide<cr>| # hide window (when terminal window is active)
# nnoremap <leader>t <cmd>!tree <bar> more<cr>
# nnoremap <leader>t <cmd>term<cr>
nnoremap <leader>t <cmd>tabnew<cr>
nnoremap <leader>T <cmd>tabclose<cr>
nnoremap <silent> <leader>vt <cmd>tab term<CR>
# nnoremap <leader>w <cmd>w<cr>
nnoremap <leader>w <cmd>update<cr>
nnoremap <leader>q <cmd>qa<cr>
nnoremap <leader>Q <cmd>qa!<cr>
nnoremap <leader>n <cmd>only<cr>
nnoremap <leader>- <c-w>s| # horizontal split
# nnoremap <leader>\| <c-w>v| # vertical split
nnoremap <leader>\ <c-w>v| # vertical split
nnoremap <leader>o <c-w>w| # next window in CCW direction
nnoremap <leader>r <cmd>registers<cr>
nnoremap <leader>m <cmd>marks<cr>

# align
vnoremap <leader>A :!column -t<cr>| # align columns
vnoremap <leader>a :s/\v(.*)=(.*)/\=printf("%-16s %s", submatch(1), submatch(2))

# Toggle group
nnoremap <leader>vs :set spell!<CR><Bar>:echo "Spell Check: " .. strpart("OffOn", 3 * &spell, 3)<CR>
# nnoremap <silent> <leader>vt <cmd>call text#Toggle()<CR>
# nnoremap <expr> <leader>vc empty(filter(getwininfo(), 'v:val.quickfix')) ? ':copen<CR>' : ':cclose<CR>'
# nnoremap <expr> <leader>vl empty(filter(getwininfo(), 'v:val.loclist')) ? ':lopen<CR>' : ':lclose<CR>'

# Vim group
nnoremap <leader>vr :new \| exec "nn <buffer> q :bd!\<cr\>" \| r ! | # redirect shell command, use :il /foo to filter lines
nnoremap <leader>vR :enew \| exec "nn <buffer> q :bd!\<cr\>" \| put = execute('map')<left><left>| # redirect vim cmd, use <leader>fi to filter
# nnoremap <leader>vl <cmd>set buflisted!<cr>
nnoremap <leader>vm <cmd>messages<cr>
# nnoremap <leader>vd <cmd>GitDiffThisFile<cr>
nnoremap <leader>ve <cmd>e $MYVIMRC<cr>
# nnoremap <leader>vz <cmd>FoldingToggle<cr>
# Following not needed: use 1<c-g> for absolute path, or <c-g> for relative path
# nnoremap <leader>vp <cmd>echo expand('%')<cr>
nnoremap <leader>vi <cmd>ShowImage<cr>
# open netrw file browser
nnoremap <leader>vf <cmd>35Lex<cr>

# ----------------------------------------
# Make <C-PageUp/Down> work in tab with terminal
def SwitchTab(dir: string)
    if &buftype == 'terminal'
        :exec "normal! \<C-\>\<C-n>"
        :exec (dir == 'up' ? "tabNext" : "tabnext")
    else
        :exec (dir == 'up' ? "tabNext" : "tabnext")
    endif
enddef
tnoremap <silent> <C-PageUp> <scriptcmd>SwitchTab('up')<cr>
tnoremap <silent> <C-PageDown> <scriptcmd>SwitchTab('down')<cr>

# # ----------------------------------------
# import '../autoload/text.vim'

# # surround ', ", and `
# vnoremap <silent> <leader>' <scriptcmd>text.Surround('''')<cr>
# vnoremap <silent> <leader>" <scriptcmd>text.Surround('"')<cr>
# vnoremap <silent> <leader>` <scriptcmd>text.Surround('`')<cr>
# nnoremap <silent> <leader>' <scriptcmd>text.Surround('''')<cr>
# nnoremap <silent> <leader>" <scriptcmd>text.Surround('"')<cr>
# nnoremap <silent> <leader>` <scriptcmd>text.Surround('`')<cr>

# # simple text objects
# # -------------------
# # i_ i. i: i, i; i| i/ i\ i* i+ i- i# i<tab>
# # a_ a. a: a, a; a| a/ a\ a* a+ a- a# a<tab>
# for char in [ '_', '.', ':', ',', ';', '<bar>', '/', '<bslash>', '*', '+', '-', '#', '<tab>' ]
#     execute 'xnoremap <silent> i' .. char .. ' <esc><scriptcmd>text.Obj("' .. char .. '", 1)<CR>'
#     execute 'xnoremap <silent> a' .. char .. ' <esc><scriptcmd>text.Obj("' .. char .. '", 0)<CR>'
#     execute 'onoremap <silent> i' .. char .. ' :normal vi' .. char .. '<CR>'
#     execute 'onoremap <silent> a' .. char .. ' :normal va' .. char .. '<CR>'
# endfor


# (:h emacs-keys) For Emacs-style editing on the command-line:
# Default keys are in :h cmdline-editing
# start of line
:cnoremap <C-A> <Home>
# back one character
:cnoremap <C-B> <Left>
# delete character under cursor
:cnoremap <C-D> <Del>
# end of line
:cnoremap <C-E> <End>
# forward one character
:cnoremap <C-F> <Right>
# recall newer command-line
:cnoremap <C-N> <Down>
# recall previous (older) command-line
:cnoremap <C-P> <Up>
# XXX: <esc> key combination causes delay in dismissing ':' command
# # esc-b is backward-one-word
# :cnoremap <Esc>b <S-Left>
# # esc-f is forward-one-word
# :cnoremap <Esc>f <S-Right>
# # back one word, Alt-B -> not used to using this
# :cnoremap â		<S-Left>
# :cnoremap ∫		<S-Left>
# :cnoremap ļ		<S-Left>
# # forward one word, Alt-F -> not used to using this
# :cnoremap æ		<S-Right>
# :cnoremap ƒ		<S-Right>
# :cnoremap ń		<S-Right>
:cnoremap <C-h> <S-Left>
:cnoremap <C-l> <S-Right>

##
## Following keybindings are useful when not using scope.vim
##

# # Open the first file in the popup menu when <cr> is entered
# augroup SelectFirstChoice | autocmd!
#     def SelectFirstChoice()
#         var context = getcmdline()->matchstr('\v\S+\ze\s')
#         if context =~ '\v^(fin|find|e|ed|edit)!{0,1}$'
#             var prefix = getcmdline()->matchstr('\v\S+\s+\zs.+')
#             if !prefix->empty()
#                 var choices = getcompletion(prefix, 'file_in_path')
#                 if !choices->empty()
#                     setcmdline($'{context} {choices[0]}')
#                 endif
#             endif
#         elseif context =~ '\v^(b|bu|buf|buffer)!{0,1}$'
#             var prefix = getcmdline()->matchstr('\v\S+\s+\zs.+')
#             if !prefix->empty()
#                 var choices = getcompletion(prefix, 'buffer')
#                 if !choices->empty()
#                     setcmdline($'{context} {choices[0]}')
#                 endif
#             endif
#         endif
#     enddef
#     autocmd CmdlineLeave : SelectFirstChoice()
# augroup END

nnoremap <leader><space> :fin **/
# ':e' automatically closes popup and selects if only one option is present; not ideal
# nnoremap <leader><space> :e **/

# find file in the parent git root directory
nnoremap <leader>ff :fin <c-r>=system("git rev-parse --show-toplevel 2>/dev/null \|\| true")->trim()<cr>/**/

nnoremap <leader>fv :fin $HOME/.vim/**/
nnoremap <leader>fV :fin $VIMRUNTIME/**/
# zsh files start with a number (01-foo.zsh), so the extra '*' at the end
nnoremap <leader>fz :fin $HOME/.zsh/**/**<left>

# note: <home>, <c-left>, <left> etc. move the cursor
nnoremap <leader>fG :vim /\v/gj **<c-left><left><left><left><left>
# <cword>
# nnoremap <leader>vG :vim /\<<c-r>=expand("<cword>")<cr>\>/gj **
# case sensitive grep
# nnoremap <leader>fG :vim /\v\C/gj **<c-left><left><left><left>
#
# send output of g// to quickfix
#  - following solution does not open qf automatically
#    g/<pattern>/caddexpr expand("%") . ":" . line(".") . ":" . getline(".")
#  - instead of above, use vimgrep
nnoremap <leader>fg :vim /\v/gj %<left><left><left><left><left>

# grep equivalents (-E is like \v magic in Vim; no need to escape |, (, ), ., ?, etc. Ex. egrep "import|more"
#   to make it case sensitive, remove '-i'
#   to search specific directory, and for C files, do <dir>/**/*.c
#   you can exclude directories or files using '~' (see zsh config file)
#   ':cw[indow]' opens (toggles) quickfix list only when it is non-empty
nnoremap <leader>g :cgetexpr system('grep -EInsi "" **/*')\|cw<c-left><left><left>
nnoremap <leader>G :cgetexpr system('grep -EInsi <c-r>=expand("<cword>")<cr> **/*')\|cw<c-left><left>

nnoremap <leader><bs> :b **/

# highlight groups ([-1] forces empty string as return value of setqflist())
nnoremap <leader>fh :<c-r>=setqflist([], ' ', #{title: 'highlight', items: execute("hi")->split("\n")->mapnew('{"text": v:val}')})[-1]<cr>copen<cr>
# others
nnoremap <leader>fk :<c-r>=setqflist([], ' ', #{title: 'keymap', items: execute("map")->split("\n")->mapnew('{"text": v:val}')})[-1]<cr>copen<cr>
nnoremap <leader>fm :<c-r>=setqflist([], ' ', #{title: 'marks', items: execute("marks")->split("\n")->mapnew('{"text": v:val}')})[-1]<cr>copen<cr>
nnoremap <leader>fr :<c-r>=setqflist([], ' ', #{title: 'registers', items: execute("registers")->split("\n")->mapnew('{"text": v:val}')})[-1]<cr>copen<cr>
nnoremap <leader>fq <cmd>chistory<cr>