My Vim

vim折腾了很久了, 好像有必要写一写我的vim配置.

大概就按照vimrc的顺序, 只挑一些好玩有趣的来讲好了. 完整的还是去github上看吧:

话说...语法高亮非常悲催因为highlight.js 不支持vimscript怎么办..

写了一个hexo插件: hexo-tag-vimhighlight, 现在可以用vim高亮代码了. 谢谢@Alick Zhao同学告知vim内置的2html.

Vundle

filetype off            " for vundle
set rtp+=~/.vim/bundle/vundle
call vundle#rc()

Bundle 'Color-Scheme-Explorer'
...
filetype plugin indent on

vundle 是一个插件管理器. 可以自动clone, update插件. 文件头先把vundle加入runtimepath中, 然后加载一堆bundle. 注意filetype要在bundle全部载入之后再开启, 否则bundle中的ftdetect会失效.

我比较BT的装了70个左右的插件, 但是我觉得它们都能用得着..而且毕竟有一半左右都是不同语言的syntax文件, 因为接触的奇怪语言比较多吧..

Fix Environment

Tmux使用了xterm-keys之后, 会使得vim无法接收C-Up一类的按键.

if &term =~ '^screen'                 " fix keymap under screen
    " tmux will send xterm-style keys when its xterm-keys option is on
    exec "set <xUp>=\e[1;*A"
    exec "set <xDown>=\e[1;*B"
    exec "set <xRight>=\e[1;*C"
    exec "set <xLeft>=\e[1;*D"
endif

对0-9a-zA-Z重置Alt键的keycode, 否则在我的环境下Alt-的快捷键无法使用.

if ! has("gui_running")                " fix alt key under terminal
    for i in range(4857+ range(6590+ range(97122)
        exec "set <A-" . nr2char(i) . ">=^[" . nr2char(i)
    endfor
endif

UI

用来用去还是默认配色最舒服, 尽管有些看烦了, 但是改了改颜色其实就很好. 另外VimDiff的时候背景色太刺眼去掉看着舒服.

set background=light
set t_Co=256
au BufEnter * if &buftype == "quickfix" | syn match Error "error:" | endif
hi LineNr ctermfg=134 guifg=#d426ff
hi VertSplit ctermbg=none ctermfg=55 cterm=none
hi CursorLineNr ctermfg=red
hi Statement ctermfg=3
hi Visual ctermbg=81 ctermfg=black cterm=none
hi MatchParen ctermbg=yellow ctermfg=black
hi Pmenu ctermfg=81 ctermbg=16
hi Cursorline ctermfg=117 cterm=italic guifg=Cyan
hi Comment ctermfg=blue guifg=#145ecc
hi Search ctermfg=red ctermbg=cyan

hi DiffAdd ctermbg=none ctermfg=LightBlue
hi DiffChange ctermbg=none ctermfg=yellow
hi DiffText ctermbg=none ctermfg=55

把类名作为C中的类型匹配出来, 看着舒服一些. 另一个匹配函数的我用了一段时间就注释掉了.

func HighlightFunctionsAndClasses()
  " syn match cCustomFunc      "\w\+\s*\((\)\@="
  " hi def link cCustomFunc      Function
  syn match cCustomClass     "\w\+\s*\(::\)\@="
  hi def link cCustomClass     cppType
endfunc
au Syntax * call HighlightFunctionsAndClasses()

Powerline 配置. 用的是一个自定义的主题. 修改主要是把NORMAL,INSERT这些弄简单了, 否则占的空间太大.

光标快要到边缘时翻页

set scrolljump=5                       " lines to scroll with cursor
set scrolloff=5                        " minimum lines to keep at border
set sidescroll=3
set sidescrolloff=3

第一行是搜索的'very magic mode', 具体区别可看帮助文档:he magic; 第二行是在visual模式下搜索

nnoremap / /\v
xnoremap / <Esc>/\%V

History

所有的cache, backup全部放在~/.vimtmp/. 开启persistent_undo, 退出后回到一个文件仍然能够进行undo.

set nobackup noswapfile
set history=200                        " command line history
if has('persistent_undo')
    set undofile                       " keep an undo record separately for every file
    set undolevels=200
    set undodir=~/.vimtmp/undo
endif
set viminfo+=n$HOME/.vimtmp/viminfo

Basic Maps

键绑定若不是prefix-free, 则可能出现歧义, 设置歧义等待时间.

因为Esc后可跟xterm keycode. ttimeoutlen不设为0的话可能导致很多机器上Esc速度慢, 非常不爽.

set timeoutlen=300                     " wait for ambiguous mapping
set ttimeoutlen=0                      " wait for xterm key escape

老按Esc键退出insert不够方便(vim默认也支持用C-C实现Esc的功能). ps: XServer下可以利用xcape将Caps Lock映射到Esc上, 要方便的多了.

inoremap <c-\> <Esc>
vnoremap <c-\> <Esc>
inoremap jj <ESC>

用分号代替冒号输命令, 会上瘾的

nnoremap ; :

fix一些常见的typo

command -bang -nargs=* Q q<bang>
command -bang -nargs=* -complete=file W w<bang> <args>
command -bang -nargs=* -complete=file Wq wq<bang> <args>
command -bang -nargs=* -complete=file WQ wq<bang> <args>

SudoWrite是sudo.vim提供的功能, 以往要是不小心用普通权限打开了root文件, 都只能先save to /tmp, 再用root恢复. 这个vimscript替你做了这件事.

cmap w!! SudoWrite %

禁用掉一些讨厌的东西:

nnoremap Q :echo<CR>
nnoremap <F1> :echo<CR>
inoremap <F1> <C-o>:echo<CR>

visual mode下用I, A命令时, 自动转为block visual mode. 很实用:

vnoremap <expr> I ForceBlockwiseVisual('I')
vnoremap <expr> A ForceBlockwiseVisual('A')
func ForceBlockwiseVisual(key)
  if mode () == 'v'
    return "\<C-v>". a:key
  elseif mode () == 'V'
    return "\<C-v>0o$". a:key
  else | return a:key | endif
endfunc

Clipboard

都是一些小技巧吧:

  • Y复制当前到行末

  • F12进入粘贴模式, 关闭所有insert键绑定

  • C-C, C-V使用系统剪贴板

  • C-Y从上一行复制一个单词下来

  • Del删除到不常用寄存器中

nnoremap Y y$
set pastetoggle=<F12>                  " toggle paste insert mode
xnoremap <c-c> "+y
inoremap <c-v> <Esc>:set paste<CR>"+p:set nopaste<CR>i
" insert word of the line above
inoremap <C-Y> <C-C>:let @z = @"<CR>mz
           \:exec 'normal!' (col('.')==1 && col('$')==1 ? 'k' : 'kl')<CR>
           \:exec (col('.')==col('$'- 1 ? 'let @" = @_' : 'normal! yw')<CR>
           \`zp:let @" = @z<CR>a
" delete to blackhole register
nnoremap <Del> "_x
xnoremap <Del> "_d
xnoremap p "_dp

Cursor Movement

使用accelerated-jk,在按住jk翻页时加快速度. 另外, accelerated-smooth-scroll 可以让C-D, C-U翻页时有滚动效果, 这样翻完更容易找到光标.

if isdirectory($HOME . "/.vim/bundle/accelerated-jk")        " a variable not assigned
    nmap j <Plug>(accelerated_jk_gj)
    nmap k <Plug>(accelerated_jk_gk)
endif
let g:accelerated_jk_acceleration_limit = 500
let g:accelerated_jk_acceleration_table = [10203035404550]

搜索时把当前结果置于屏幕中央并打开折叠:

nnoremap n nzzzv
nnoremap N Nzzzv

easymotion 可以精确快速跳转到屏幕内任意指定字符处.

通常情况下C-U, C-W删除行, 词后无法撤销, 非常郁闷. 可以用这个map:

inoremap <c-u> <c-g>u<c-u>
inoremap <c-w> <c-g>u<c-w>

打开文件时恢复到上次编辑的位置:

au BufReadPost *
           \ if line("'\""> 0 && line("'\""<= line("$") |
           \   exe "normal g`\"" |
           \ endif

光标一次移动行数较多时自动打开cursorline, 使得用户更容易找到光标. 但这个在编辑大文件的时候对速度影响比较大.

func HintCursorLine(opr)
    if a:opr == 0            " clear cursorline
        set nocursorline
        if exists("&cc") | set cc= | endif
        return
    endif

    if ! exists('g:last_line') | let g:last_line = -1 | end
    if ! exists('g:last_pos')  | let g:last_pos  = -1 | end
    if ! exists('g:last_win')  | let g:last_win  = -1 | end
    let cur_pos  = winline()
    let cur_line = line(".")
    let diff = max(abs(g:last_line - cur_line)abs(g:last_pos - cur_pos) ])
    if g:last_win != winnr() || diff > 3
        set cursorline
    endif
    let g:last_pos  = cur_pos
    let g:last_line = cur_line
    let g:last_win  = winnr()
endfunc
au CursorMoved,BufWinEnter * call HintCursorLine(1)

Auto Fill Brackets

这是一个不少人不喜欢的功能..但是配的合适的话, 个人觉得还是有效率提升:

func AutoPair(open, close)
  let line = getline('.')
  if col('.') > strlen(line) || index([' '']'')''}'], line[col('.') - 1]) > 0
    return a:open . a:close . "\<ESC>i"
  else
    return a:open
  endif
endf
func ClosePair(char)
  return (getline('.')[col('.') - 1== a:char ? "\<Right>" : a:char)
endf
inoremap <expr> ( AutoPair('(', ')')
inoremap <expr> ) ClosePair(')')
inoremap <expr> [ AutoPair('[', ']')
inoremap <expr> ] ClosePair(']')
inoremap <expr> { AutoPair('{', '}')
inoremap <expr> } ClosePair('}')

这两个函数实现的功能是, 按左括号时,判断右边字符, 满足一定条件时补全右括号. 按右括号时看左边是否是对应的左括号.

About Chinese

输入中文标点时自动替换:

imap ( (
imap ) )
imap 』 }
imap 『 {
imap 【 [
imap 】 ]
imap 。 .
imap , ,
imap ; ;
imap : :
imap “ "
imap ” "
imap ‘ '
imap ’ '
imap ? ?
imap ! !
imap 》 >
imap 《 <
imap 、 /
imap ¥ $
map : :

将中文标点及一些其他符号替换为 $\LaTeX$ 下使用的格式

func Replace_Chn()                     " for writing latex
    let chinese={"(" : "(" , ")" : ")" , "," : ","";" : ";"":" : ":""?" : "?""!" : "!""“" : "\"""’" : "'" ,"”" : "\"""℃" : "\\\\textcelsius""μ" : "$\\\\mu$"}
    for i in keys(chinese)
        silent! exec '%substitute/' . i . '/'. chinese[i. '/g'
    endfor
endfunc
nnoremap <leader>sch :call Replace_Chn()<cr>

离开insert时将输入法切为英文, 进入时若光标下字符是中文则切输入法(仅适用于fcitx):

func Fcitx_enter()
    if (getline('.')[col('.') - 1>= "\x80" || getline('.')[col('.') - 2>= "\x80")
        call system("fcitx-remote -o")
  endif
endfun
autocmd InsertLeave * call system("fcitx-remote -c")
autocmd InsertEnter * call Fcitx_enter()

我写的中文搜索插件PinyinSearch, 按照首字母搜索中文

nmap <Leader>ps :call PinyinSearch()<CR>
nnoremap ? :call PinyinSearch()<CR>
nmap <Leader>pn :call PinyinNext()<CR>
let g:PinyinSearch_Dict = $HOME . "/.vim/bundle/vim-PinyinSearch/PinyinSearch.dict"

Some Small Functions

删除行末空格

func DeleteTrailingWhiteSpace()
    normal mZ
    %s/\s\+$//e
    normal `Z
endfunc
au BufWrite * if &ft != 'mkd' | call DeleteTrailingWhiteSpace() | endif

Extract/Inline Variable,在IDE中很常见, 最初在rails社区流行的功能.

func ExtractVariable()
    let name = input("Variable name: ")
    if name == '' | return | endif
    " Enter visual mode (not sure why this is needed since we're already in visual mode anyway)
    normal! gv

    exec "normal c" . name
    exec "normal! O" . name . " = "
    normal! $p
endfunction
func InlineVariable()
    " Copy the variable under the cursor into the 'a' register
    exec "normal \"zyiw"
    normal 2daW
    exec "normal \"xd$"
    normal dd
    " Go to the end of the previous line so we can start our search for the
    " usage of the variable to replace. Doing '0' instead of 'k$' doesn't
    " work; I'm not sure why.
    normal k$
    exec '/\<' . @z . '\>'
    exec ':.s/\<' . @z . '\>/' . @x
endfunction
xnoremap <leader>rv :call ExtractVariable()<cr>
nnoremap <leader>ri :call InlineVariable()<cr>

自动更新文件头处的修改时间, 文件名.

fun LastMod()
    let l:line = line(".")                            " save cursor position
    let l:col = col(".")
    let l = min([line("$")8])
    exec '1,' . l . 'substitute/' . '^\(.*Date:\).*$' . '/\1 ' . strftime('%a %b %d %H:%M:%S %Y %z') . '/e'
    exec '1,' . l . 'substitute/' . '^\(.*File:\).*$' . '/\1 ' . expand('<afile>:t') . '/e'
    call cursor(l:line, l:col)
endfun
au BufWritePre,FileWritePre * call LastMod()

将当前正在编辑但未保存的文件与已保存的做diff

func DiffWithSaved()
    let ft=&filetype
    diffthis
    vnew | r # | normal! 1Gdd
    diffthis
    exe "setlocal bt=nofile bh=wipe nobl noswf ro ft=" . ft
endfunc
com DiffSaved call DiffWithSaved()
nnoremap <Leader>df :call DiffWithSaved()<CR>

复制当前文件, 备份当前文件:

nmap <Leader>cp :!xclip -i -selection clipboard % <CR><CR>
nnoremap <Leader>bk :!mkdir -p vim_backup; cp % vim_backup/%_bk --backup=numbered <CR>

光标在链接上时打开浏览器

func Browser ()
    let line0 = getline (".")
    let line  = matchstr (line0, "http[^ ,;\t)]*")
    if line==""
        let line = matchstr (line0, "ftp[^ ,;\t)]*")
    endif
    if line==""
        let line = matchstr (line0, "www\.[^ ,;\t)]*")
    endif
    exec "!chromium ".line
    " TODO chrome cannot be run as root
endfunc
nnoremap <Leader>ch :call Browser ()<CR>

一些常用功能: 切换是否wrap line, 刷新屏幕, 清空搜索高亮, 在字典里查当前单词.

nmap <Leader>nw :set wrap!<CR>
nmap <Leader>rd :redraw!<CR>
nnoremap <silent> <Leader>no :noh <CR>:call clearmatches()<CR>:silent! SearchBuffersReset<CR>
nnoremap <Leader>sd :! sdcv `echo <cword> \| sed -e 's/[^[:alnum:]]//g'` <CR>

Completion

找了一个英语词典, C-X C-K补全单词.

set dict+=$HOME/.vim/static/dict_with_cases          " use c-X c-K to open dictionary completion

补全神器YCM 是一个补全引擎, 自身提供了C/C++/C#/Python的semantic completion, 非常靠谱, 同时作为一个引擎, 可以与其他补全插件合作. YCM配置很轻松, 就是编译稍麻烦.

由于要对付一些乱七八糟的文件类型,我还是保留了 neocomplcache. 对于nginx/tmux/shell/vimscript这些文件, neo都有默认的关键字支持, 而ycm对此无能为力.

虽然YCM自带jedi, 但是原装的jedi有参数列表popup. 重命名功能也很好.

let g:jedi#use_tabs_not_buffers = 0
let g:jedi#rename_command = "<Leader>rn"

写Java需要eclipse补全, eclim可以在vim中调用eclipse的补全方法, java语法垃圾太多, 没有补全几乎无法工作, eclim大大提高了工作效率. 为了跟ycm配合, 将eclim的补全方法配置成omni即可.

sparkup 是快速插入html标签的, 跟以前的一个zencoding 用法差不多但是功能更强. 虽然我不怎么写html.

具体的配置有些长, 就不贴了.

Programming

编辑新文件时根据扩展名生成scaffolds, 包括文件头(时间, 作者, 文件名), 文件编码, 常用库等. 这个比较无聊也不贴了...

编译相关的东西. Make()在没有Makefile时就调用make命令. FindMakefile()会一直向上级找Makefile (有的时候 Makefile会藏得很深..). 对不同文件类型设了makeprg, 包括dot, gnuplot可以实现运行自动预览了.

func Make()                        " silent make with quickfix window popup
    if &ft == 'cpp'
        if filereadable(getcwd() . "/Makefile")
            let &makeprg="make"
        elseif  filereadable(getcwd() . "/../Makefile")
            let &makeprg="make -C .."
        endif
    endif
    make
    " silent make ?
    redraw!
    for i in getqflist()
        if i['valid']
            cwin | winc p | return
        endif
    endfor
endfunc

func FindMakefile()
    exec "cd " . expand ("%:p:h")
    while ! filereadable(getcwd() . "/Makefile") && getcwd () != "/"
        cd ..
    endw
    :!make
endfunc
au Filetype gnuplot let &makeprg="gnuplot % ; feh ./*"
au Filetype dot let &makeprg="dot -Tpng -O -v % ; feh %.png"
au Filetype php let &makeprg="php %"
au Filetype r let &makeprg="R <% --vanilla"
func InstantRun()
    if &ft == 'python'
        if matchstr(getline(1)'python2') == ""
            :!python %
        else | :!python2 %
    endif
    elseif &ft == 'ruby' | :!ruby %
    elseif &ft == 'sh' | :!bash %
    elseif &ft == 'cpp' | :!gdb -tui %<
    elseif &ft == 'java' | :! java %<
    elseif &ft == 'javascript' | :! node %
    elseif &ft == 'coffee' | :! coffee %
  else | call Make() | endif
endfunc
nnoremap <Leader>rr :call InstantRun() <CR>
nnoremap <Leader>mk :call Make()<CR>
nnoremap <Leader>mr :!make run <CR>
nnoremap <Leader>make :call FindMakefile()<CR>

对于$\LaTeX$, 加了很多乱七八糟的映射. 以及用了 Latex-Box. 不过还是自己写的部分用的顺手..尤其是几个常用环境加了很多scaffolds Latex-Box支持将一个环境变为text-objects, 也比较好用.具体的太多了,有80行, 也不贴了..

我使用make构建tex文档, mupdf做阅读器, 下面这条配置可在保存时自动编译并刷新mupdf, 这个很爽的. 当然如果你用evince之类的话, 自己就会刷新..mupdf虽然快, 兼容性稳定性还是差一些.

au BufWritePost *.tex call system("zsh -c 'make; killall -SIGHUP mupdf > /dev/null 2 >&1' &")

对于C/C++, 高亮一些我常用的自定义类型和标准库类型.

syn keyword cppType real_t Vec Vec2D Vector Matrix Plane Sphere Geometry Ray Color Img imgptr
syn keyword cppSTL priority_queue hypot stringstream isnormal isfinite isnan shared_ptr make_shared numeric_limits move
syn keyword cppSTLType T

编辑Java文件时, 也高亮一些常用类型, 看着舒服多了:

syn keyword javaType String Integer Double Pair Collection Collections List Boolean Triple ArrayList Entry LinkedList Map HashMap Set HashSet TreeSet TreeMap Iterator Iterable Comparator Arrays ListIterator Vector File Character Object Exception Random

编辑shell脚本自动chmod +x

au BufWritePost *
            \ if getline(1=~ "^#!/bin/[a-z]*sh" |
            \   exe "silent !chmod a+x <afile>" |
            \ endif

打开doc, pdf, javaclass文件

au BufNewFile,BufRead *.txt,*.doc,*.pdf setl ft=txt
au BufReadPre *.doc,*.class,*.pdf setl ro
au BufReadPost *.doc silent %!antiword "%"
au BufRead *.class exe 'silent %!javap -c "%"' | setl ft=java
au BufReadPost *.pdf silent %!pdftotext -nopgbrk "%" -

Other Plugins

fswitch的功能是 a.vim的超集, 是一个用来切换C++源文件/头文件的好东西. 我写C++的习惯一般会开include目录, 因此修改它的搜索路径.

au BufEnter *.cpp let b:fswitchdst = 'hpp,h' | let b:fswitchlocs = './,./include,../include'
au BufEnter *.cc let b:fswitchdst = 'hh,h' | let b:fswitchlocs = './include,./,../include'
au BufEnter *.hh let b:fswitchdst = 'cc,cpp' | let b:fswitchlocs = '../,./'
au BufEnter *.h let b:fswitchdst = 'cpp,cc' | let b:fswitchlocs = './,../'

tpope的surround 应该是必备了.

xmap s <Plug>VSurround

evervim 使得vim下可以用markdown编辑evernote, 又一个windowsonly的东西被替代了. 它需要读取evernote api key

command NOTE EvervimNotebookList
if filereadable($HOME . "/.evervim/secret.vim") | source ~/.evervim/secret.vim | endif

colorv 可以打开一个调色盘, 看当前高亮的颜色或查找其他颜色的code. 把它的cache也放到vimtmp下

" <Leader>cv to use ColorV
let g:colorv_cache_file = $HOME . '/.vimtmp/colorv_cache_file'
let g:colorv_cache_fav = $HOME . '/.vimtmp/colorv_cache_fav'

NerdCommenter 添加注视很方便, 但是默认键绑定不习惯. 常用的键绑定其实只有两个所以自己wrap了一下.

let g:NERDCreateDefaultMappings = 0
nmap <Leader>cc <Plug>NERDCommenterSexy
xmap <Leader>cc <Plug>NERDCommenterSexy
nmap <Leader>cl <Plug>NERDCommenterAlignLeft
xmap <Leader>cl <Plug>NERDCommenterAlignLeft
nmap <Leader>uc <Plug>NERDCommenterUncomment
xmap <Leader>uc <Plug>NERDCommenterUncomment

expand-region visual mode下向外根据预定规则寻找text-object扩展选择范围.

if exists("*expand_region#custom_text_objects")
    call expand_region#custom_text_objects({
      \ "\/\\n\\n\<CR>"1,
      \ 'a]' :1'ab' :1'aB' :1'a"' :1'a'''1,
      \ 'ii' :0'ai' :0,
      \ 'ic' :0'ac' :0,
      \ }) | endif
xmap K <Plug>(expand_region_expand)
xmap J <Plug>(expand_region_shrink)

MultipleSearch 允许多个关键词一起搜索, 用不同颜色高亮. 星号搜光标下单词.

nnoremap <silent> * :execute ':Search \<' . expand('<cword>') . '\>'<cr>
nnoremap <Leader>/ :Search<Space>
let g:MultipleSearchMaxColors = 16

grep.vim , 一般只用在项目中递归grep的功能.

nnoremap <Leader>gr :Regrep <CR><CR><CR><CR>
let Grep_Skip_Files = '.tags tags'
let Grep_Skip_Dirs  = 'node_modules build'

ctrlp 查找东西极为方便.可以跳转文件, 跳转buffer, 跳转mru.

rainbow_parentheses 使得匹配的层叠括号被不同颜色高亮, 确实能看的清楚一点.

对xml, js, css, html的beautify. 使用了js-beautify, 基于node的beautify引擎.

nmap <Leader>xml :%s/></>\r</g<CR>gg=G
nmap <Leader>js :call JsBeautify()<CR>:set ft=js<CR>
nmap <Leader>css :call CSSBeautify()<CR>
nmap <Leader>html :call HtmlBeautify()<CR>
let s:rootDir           = fnamemodify(expand("<sfile>"), ":h")
let g:jsbeautify_file   = fnameescape(s:rootDir."/.vim/bundle/js-beautify/beautify.js")
let g:htmlbeautify_file = fnameescape(s:rootDir."/.vim/bundle/js-beautify/beautify-html.js")
let g:cssbeautify_file  = fnameescape(s:rootDir."/.vim/bundle/js-beautify/beautify-css.js")

indentLine, 在缩进处利用conceal功能实现标注, 免得缩进多了看不见. 与list功能配合使用更佳. 我为这个项目提交过一个commit..

let g:indentLine_enabled = 0
let g:indentLine_color_term = 239
let g:indentLine_color_gui = '#A4E57E'
nnoremap <leader>ig :IndentLinesToggle<CR>:set list! lcs=tab:\\|\<Space><CR>

tag浏览使用友好的tagbar, 文件管理使用NERDTree.

将修改历史展示成树状供撤销, gundo 也是必备插件了.

shell相关的, ConqueTerm, Clam, Screen 各有优势吧.

Comments