Как стать автором
Обновить

Продвинутая настройка VIM

Время на прочтение13 мин
Количество просмотров46K
Одно из правил эффективного использования редактора гласит следующее — определите, на что у Вас тратится больше всего времени при наборе текста, и улучшите это.
Как показывает практика, часто пользователи этого редактора ограничиваются установкой опций, коих конечно не мало. Затем ставят какой-нибудь плагин-мега-пак по советам знатоков, и вроде бы все устраивает, кроме… первого, второго, третьего…
Но ведь если пойти дальше, можно обнаружить бесконечный потенциал для увеличения производительности в использовании своего редактора.



В этой статье я попытаюсь описать немного продвинутый способ настройки Vim.
Мы рассмотрим с Вами внутренний скриптинг и поймем, что ничего в нем нет страшного, обычный скриптовый язык.
Данный материал рассчитан на довольно подготовленных пользователях редактора Vim. Для тех, кто разобрался, что такое режимы редактора, буферы, окна. Статья написана в стиле «Одна глава — один конкретный рецепт — одно описание синтаксической структуры языка».


История изменений или оператор if else


Приведем пример использования оператора if в Вашем vimrc для установки опций
if version >= 700
    set history=64
    set undolevels=128
    set undodir=~/.vim/undodir/
    set undofile
    set undolevels=1000
    set undoreload=10000
endif

Данный кусок кода включает весьма полезные возможности, доступные начиная с версии 7.00: после того, как вы закрываете редактор(а если точнее, то текущий буффер), в предыдущих версиях история UNDO-REDO терялась. Начиная же с 7.00 появилась возможность записи этой истории в служебные файлы по каждому ранее открытому буфферу.
То есть теперь вы можете сменить буффер, закрыть окно, вообще выключить редактор, но открыв заново какой-либо файл, история Ваших изменений восстановится.

Быстрое переключение буферов или создание своей функции


Переключение между загруженными буферами должно быть быстрым. Не совсем удобно постоянно набирать :bn, :bp, :b#. Поэтому создадим свою функцию переключения и повесим на горячие клавиши этот функционал.
function! ChangeBuf(cmd)
    if (&modified && &modifiable)
        execute ":w"
    endif
    execute a:cmd
endfunction
nnoremap <silent> <C-o> :call ChangeBuf(":b#")<CR>
nnoremap <silent> <C-n> :call ChangeBuf(":bn")<CR>
nnoremap <silent> <C-p> :call ChangeBuf(":bp")<CR>

Как вы знаете, если файл модифицирован, команды :bn, :bp, b# не сработают и выведут предупреждение о том, что надо его сохранить. Для этого мы и пишем эту функцию, в которой осуществляется проверка, модифицирован ли файл и может ли он быть модифицирован вообще.
Здесь мы создаем функцию, аргумент которой примет как раз те команды по переключению буферов, описанный выше.
nnoremap создает привязку определенной комбинации клавиш для какого либо действия. Аргумент \<silent\> — подавить echo вывод.

Здесь надо дать некоторое пояснение по переменным в .vimrc, а именно практически все они начинаются с какого либо префикса, отделенного от имени двоеточием. Префикс означает область видимости. Вот основные префиксы:
a:var — аргумент функции
b:var — переменная для текущего буфера
g:var — глобальная переменная
l:var — переменная, объявленная в теле функции
v:var — глобальная определенная в самом редакторе Vim


Список буферов или цикл for


Мне не очень нравится вывод буферов по команде :ls, хотя бы из-за того, что вывод многострочный. По-этому разберем пример с циклом for для вывода списка открытых буферов в одной строке. Преимуществом такого решения является то, что я могу вызвать эту функцию где угодно, в том числе в методе, описанном в предыдущем разделе. Получится, что при смене текущего буфера сразу будет отображаться список других открытых буферов.
function! BufList()
    let status = ""
    for i in range(1, last_buffer_nr()+1)
        if bufnr("%") == i 
            let status = status . '   ' . '[' . bufname(i) . ']' "объединяем строки
            continue
        endif
        if buflisted(i)
            let status = status . '   ' . bufname(i)
        endif
    endfor
    return status
endfunction

Здесь мы просто формируем строку со списком открытых буферов. Текущий буфер выделяем в квадратных скобках.
Как мы видим, цикл for весьма схож с циклом из того же Python'а

Здесь надо указать, что ряд функций редактора Vim принимают в качестве аргумента так называемое выражение. Для функции bufnr() например выражением может быть, например символ "%" — даст номер текущего буфера, "$" — даст номер последнего буфера. По каждой функции лучше все-таки смотреть :help func()


Автоматически исполняемый скрипт или чтение данных из текущего буфера


Я довольно часто начинаю писать новые скрипты. И мне удобно, что файл может быть сразу исполняемым.
function ModeChange()
    if getline(1) =~ "^#!"
        if getline(1) =~ "bin/"
            silent !chmod a+x <afile>
        endif
    endif
endfunction
au BufWritePost * call ModeChange()

Здесь мы берем и читаем первую строку из файла, и если она начинается с '#!' и в ней есть 'bin/', то делаем файл исполняемым. После вешаем автокоманду на событие BufWritePost.

Автоматическая заготовка скрипта или вставка в текущий буффер


Этот пример связан с предыдущим. Если мы начинаем писать новый скрипт на python'е, было бы удобно, если сразу в нем была заготовка, например, функция main, некоторые import'ы, строка определения интерпретатора. Продемонстрируем.
function! WritePyinit()
    let @q = "
    \#\!/usr/bin/env python\n\#-*- encoding: utf-8 -*-\n\nimport sys, warnings\n\nwarnings.simplefilter('always')\n\ndef main(argv=sys.argv):\n    pass\n\nif __name__ == \"__main__\":\n           
sys.exit(main())\n"
    execute "0put q"
endfunction
autocmd BufNewFile *.py call WritePyinit()

Все просто. Если у Вас новый файл *.py, в него будет добавлен стандартный код.
Новый файл test.py
#!/usr/bin/env python
#-*- encoding: utf-8 -*-

import sys, warnings

warnings.simplefilter('always')

def main(argv=sys.argv):
    pass

if __name__ == "__main__":
    sys.exit(main())



Автоматическая документация или пишем vimrc с синтаксисом Python'а


Эта статья была бы не полной, если бы я не показал, как можно делать вставки в .vimrc на другом языке программирования. Покажу это на примере Python.
Код надо документировать. На Python документация часто пишется в виде DocStrings. Покажем пример, как можно по комбинации клавши автоматически переноситься к тому месту, где должна быть документация.
Автодокументация
function! WriteDocstrings()
python <<EOF
import vim
import re
linenr = vim.current.window.cursor[0]
indentr = vim.current.window.cursor[0]
line = vim.current.line
n = 0
for i in line:
    if i != ' ':
       break
    n += 1
if len(line) == 0:
    n = 0
vim.current.buffer.append(' '*n + ' '*4 + '"""', linenr)
vim.current.buffer.append(' '*n + ' '*4 + '', linenr)
vim.current.buffer.append(' '*n + ' '*4 + '"""', linenr)
vim.current.window.cursor = (vim.current.window.cursor[0]+2, n+4)
EOF
endfunction

Опять же, ничего хитрого, обычные HERED-Docs.
Здесь мы используем модуль для Python vim. Далее выискиваем начало текущего блока, вставляем туда кавычки для документации и переводим курсор для в начало будущей документации.

Надо учесть то, что для работы этого кода, вам необходима поддержка python в Вашем vim. Проверить это можно следующим образом:
vim --version | grep '+python'

Если есть поддержка, то все хорошо. Если нет, надо либо собрать Vim самому, либо поставить другой пакет. В Debian/Ubuntu/Mint я рекомендую ставить пакет vim-nox
apt-get install nox



Заключение


Статья получилась довольно объемной. Надеюсь, я показал, что маленькие особенности в Ваш редактор можно добавлять довольно просто.
Теперь, разобравшись с основными принципами продвинутой настройки редактора, Вы сможете написать более полезные дополнения, например, комментирование блока выделенного текста, открытие документации каких-либо функций.

Список полезных материалов

Официальный сайт Vim
VIMDOC, с гиперссылками
Документация по модулю vim для python

UPD1: чищеный от приватки vimrc
"
" Comments/uncomments strings and selected text
" When buffer changing try to save it
" Convinient view of statusline
" AutoResave sessions on exit
" Auto settings for DJANGO omni completion
"
"Mapped Keys
" Mode  Combination Description
" N     <C-n>       Next buffer with saving if possible
" N     <C-p>       Prev buffer with saving if possible
" N     <C-o>       Last viewed buffer
" N     <C-e>       Clear highlighting after searching
" I     <C-q>       Set normal mode
" N     <C-c>       Comment Lines
" N     <C-u>       Uncomment Lines
" I     <C-Space>   Show popup menu OmniCompletion
" I     <Tab>       If popupmenu is showed list it elements
" NIV   <F5>        Bonding Mode(useful when pasting from X-buffer)
" I     <A-h>       Left in insert mode
" I     <A-j>       Down in insert mode
" I     <A-k>       Up in insert mode
" I     <A-l>       Right in insert mode
" N     ,p          Paste after
" N     ,P          Paste before
" N     <C-j>       Down in long line
" N     <C-k>       Up in long line
" V     <C-c>       Comment Line
" V     <C-u>       Uncomment Line

filetype on
filetype plugin on

set tabstop=4
set shiftwidth=4
set smarttab
set expandtab
set softtabstop=4
set autoindent
let python_highlight_all = 1
set t_Co=256
autocmd FileType html,xhtml,xml,htmldjango,htmljinja,eruby,mako setlocal noexpandtab
autocmd FileType *.py set tw=80
autocmd FileType python nnoremap <C-a> :call WriteDocstrings()<CR>
"nnoremap <C-a> :call WriteDocstrings()<CR>
autocmd BufWritePre *.py normal m`:%s/\s\+$//e ``
autocmd BufRead *.py set smartindent cinwords=if,elif,else,for,while,try,except,finally,def,class
autocmd BufNewFile *.py call WritePyinit()
autocmd VimLeave * !reset
set nu
set ruler
set mousehide
syntax on
set backspace=indent,eol,start whichwrap+=<,>,[,]
set showtabline=0
set foldcolumn=1
set wrap
set linebreak
set nobackup
set noswapfile
set encoding=utf-8
set fileencodings=utf8,cp1251
"Searchig options
set showmatch
set hlsearch
set incsearch
"Layouts
set fencs=utf-8,cp1251,koi8-r,ucs-2,cp866
"memory, history, undotree
if version >= 700
    set history=64
    set undolevels=128
    set undodir=~/.vim/undodir/
    set undofile
    set undolevels=1000
    set undoreload=10000
endif
"Sessions - сохранение текущих буферов, регистров.
set sessionoptions=buffers,tabpages,help,blank,globals,localoptions,sesdir,slash,options
function! SaveSession(...)
    if v:this_session != ""
        function Tmp(filename)
            execute ":mksession! ".v:this_session
        endfunction
        autocmd VimLeavePre * :call Tmp("xxx")
    endif
endfunction
au SessionLoadPost * :call SaveSession()
"Titles, statuses
set laststatus=2
set showtabline=2
set title
set statusline=%1*%m%*%2*%r%*%3*%h%w%*%{expand(\"%:p:~\")}\ %<
set statusline+=%=Col:%3*%03c%*\ Ln:%3*%04l/%04L%*
set statusline+=%(\ File:%3*%{join(filter([&filetype,&fileformat!=split(&fileformats,\",\")[0]?&fileformat:\"\",&fileencoding!=split(&fileencodings,\",\")[0]?&fileencoding:\"\"],\"!empty(v:val)\"),\"/\")}%*%)
set titlestring=%t%(\ %m%)%(\ %r%)%(\ %h%)%(\ %w%)%(\ (%{expand(\"%:p:~:h\")})%)\ -\ VIM

"autocmd VimLeavePre * silent mksession! ~/.vim/lastSession.vim
"autocmd VimEnter * silent source! ~/.vim/lastSession.vim
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
set langmap=йq,цw,уe,кr,еt,нy,гu,шi,щo,зp,х[,ъ],фa,ыs,вd,аf,пg,рh,оj,лk,дl,ж\\;,э',яz,чx,сc,мv,иb,тn,ьm,б\\,,ю.,ё`,ЙQ,ЦW,УE,КR,ЕT,НY,ГU,ШI,ЩO,ЗP,Х{,Ъ},ФA,ЫS,ВD,АF,ПG,РH,ОJ,ЛK,ДL,Ж:,Э\\",ЯZ,ЧX,СC,МV,ИB,ТN,ЬM,Б<,Ю>,Ё~
"Buffers
function! ChangeBuf(cmd)
    if (&modified && &modifiable)
        execute ":w"
    endif
    execute a:cmd
endfunction
nnoremap <silent> <C-o> :call ChangeBuf(":b#")<CR>
nnoremap <silent> <C-n> :call ChangeBuf(":bn")<CR>
nnoremap <silent> <C-p> :call ChangeBuf(":bp")<CR>
nnoremap <C-CR> O<ESC>j
inoremap <C-q> <ESC>
nnoremap <C-e> :nohlsearch<CR>
inoremap <C-e> <ESC>I
inoremap <C-a> <ESC>A
function! BufList()
    let status = ""
    for i in range(1, last_buffer_nr()+1)
        if bufnr("%") == i
            let status = status . '   ' . '[' . bufname(i) . ']'
            continue
        endif
        if buflisted(i)
            let status = status . '   ' . bufname(i)
        endif
    endfor
    return status
endfunction
"Copy/Paste
if has("clipboard")
    set clipboard=autoselect
endif
"Autoaddition
set formatoptions-=o

"scripting
function ModeChange()
    if getline(1) =~ "^#!"
        if getline(1) =~ "bin/"
            silent !chmod a+x <afile>
        endif
    endif
endfunction
au BufWritePost * call ModeChange()
if expand("%:t") =~ "py$"
    set makeprg=python 
endif
if expand("%:t") =~ "sh$"
    set makeprg=/bin/bash 
endif

if !has("gui_running")
    imap <C-@> <C-X><C-O>
else
    imap <C-Space> <C-X><C-O>
endif

filetype plugin on
set ofu=syntaxcomplete#Complete
set complete=.,b,t,k,u,k
set completeopt-=preview 
set completeopt+=longest 
autocmd FileType python set omnifunc=pythoncomplete#Complete
autocmd FileType javascript set omnifunc=javascriptcomplete#CompleteJS
autocmd FileType html set omnifunc=htmlcomplete#CompleteTags
autocmd FileType css set omnifunc=csscomplete#CompleteCSS
if expand("%:t") =~ "^.*\.py$"
   let $PYTHONPATH = fnamemodify("%", ":p:h:h")
   let $DJANGO_SETTINGS_MODULE = fnamemodify("%", ":p:h:t").".settings"
endif
set completeopt=longest,menuone
"Режим склейки при вставке
"set paste 
"set nopaste
"set invpaste
set pastetoggle=<F5>

"Folding
set foldenable
set foldmethod=syntax
autocmd FileType tex set foldmethod=indent


"Цветовая схема
"color blackboard
let g:solarized_termcolors=256
colorscheme solarized
set background=dark


"Remaps
inoremap <A-h> <C-o>h
inoremap <A-j> <C-o>j
inoremap <A-k> <C-o>k
inoremap <A-l> <C-o>l

nnoremap ,p o<ESC>p
nnoremap ,P O<ESC>p

nnoremap <C-j> gj
nnoremap <C-k> gk

"Автокомменты
nnoremap  <C-c> :call CommentLine()<cr>
nnoremap  <C-u> :call UnCommentLine()<cr>
vmap  <C-c> :call CommentLine()<cr>
vmap  <C-u> :call UnCommentLine()<cr>
"скрипты

function! WritePyinit()
    let @q = "
    \#\!/usr/bin/env python\n\#-*- encoding: utf-8 -*-\n\nimport sys, warnings\n\nwarnings.simplefilter('always')\n\ndef main(argv=sys.argv):\n    pass\n\nif __name__ == \"__main__\":\n           sys.exit(main())\n"
    execute "0put q"
endfunction

function! WriteDocstrings()
if !has('python')
    echo "Error: Required vim compiled with +python"
    finish
endif
python <<EOF
import vim
import re
linenr = vim.current.window.cursor[0]
indentr = vim.current.window.cursor[0]
line = vim.current.line
n = 0
for i in line:
    if i != ' ':
       break
    n += 1
if len(line) == 0:
    n = 0
vim.current.buffer.append(' '*n + ' '*4 + '"""', linenr)
vim.current.buffer.append(' '*n + ' '*4 + '', linenr)
vim.current.buffer.append(' '*n + ' '*4 + '"""', linenr)
vim.current.window.cursor = (vim.current.window.cursor[0]+2, n+4)
EOF
endfunction
""""""""""""""""
"Для комметариев
""""""""""""""""
function __is_django_template()
    let l:a = getpos(".")
    if search("{\%.*\%}", '', line("$")) != 0
        let b:b = cursor(l:a[1], l:a[2], "off")
        return 1
    endif
    return 0
endfunction

function RetFileType()
    let file_name = buffer_name("%")
    if file_name =~ '\.vim'
        return ["\"", ""]
    elseif __is_django_template() == 1
        return ['{% comment %}' , '{% endcomment %}']
    elseif file_name =~ '\.html$' || file_name =~ '\.xhtml$' || file_name =~ '\.xml'
        return ["<!--", "-->"]
    endif
    return ["#", ""]
endfunction
au BufEnter * let b:comment = RetFileType()
function! CommentLine()
    let stsymbol = b:comment[0]
    let endsymbol = b:comment[1]
    execute ":silent! normal 0i" . stsymbol . "\<ESC>A" . endsymbol . "\<ESC>"
endfunction
function! UnCommentLine()
    let file_name = buffer_name("%")
    let stsymbol = b:comment[0]
    let endsymbol = b:comment[0]
    execute ":silent! normal :s/^\s*" . stsymbol . "//\<CR>"
    execute ":silent! normal :s/\s*" . endsymbol . "\s*$//\<CR>"
endfunction
let ropevim_vim_completion=1

function! CmdLine(str)
    exe "menu Foo.Bar :" . a:str
    emenu Foo.Bar
    unmenu Foo
endfunction 

" From an idea by Michael Naumann
function! VisualSearch(direction) range
    let l:saved_reg = @"
    execute "normal! vgvy"

    let l:pattern = escape(@", \/.*$^~[])
    let l:pattern = substitute(l:pattern, "\n$", "", "")

    if a:direction == b
        execute "normal ?" . l:pattern . "^M"
    elseif a:direction == gv
        call CmdLine("vimgrep " . /. l:pattern . / .  **/*.)
    elseif a:direction == f
        execute "normal /" . l:pattern . "^M"
    endif

    let @/ = l:pattern
    let @" = l:saved_reg
endfunction

"Basically you press * or # to search for the current selection
vnoremap <silent> * :call VisualSearch('f')<CR>
vnoremap <silent> # :call VisualSearch('b')<CR>
vnoremap <silent> gv :call VisualSearch('gv')<CR>
"PYDOC
if exists('*s:ShowPyDoc') && g:pydoc_perform_mappings
    call s:PerformMappings()
    finish
endif

if !exists('g:pydoc_perform_mappings')
    let g:pydoc_perform_mappings = 1
endif

if !exists('g:pydoc_highlight')
    let g:pydoc_highlight = 1
endif

if !exists('g:pydoc_cmd')
    let g:pydoc_cmd = 'pydoc'
endif

if !exists('g:pydoc_open_cmd')
    let g:pydoc_open_cmd = 'split'
endif

setlocal switchbuf=useopen
highlight pydoc cterm=reverse gui=reverse

function s:ShowPyDoc(name, type)
    if a:name == ''
        return
    endif

    if g:pydoc_open_cmd == 'split'
        let l:pydoc_wh = 10
    endif

    if bufloaded("__doc__")
        let l:buf_is_new = 0
        if bufname("%") == "__doc__"
            " The current buffer is __doc__, thus do not
            " recreate nor resize it
            let l:pydoc_wh = -1
        else
            " If the __doc__ buffer is open, jump to it
            silent execute "sbuffer" bufnr("__doc__")
            let l:pydoc_wh = -1
        endif
    else
        let l:buf_is_new = 1
        silent execute g:pydoc_open_cmd '__doc__'
        if g:pydoc_perform_mappings
            call s:PerformMappings()
        endif
    endif

    setlocal modifiable
    setlocal noswapfile
    setlocal buftype=nofile
    setlocal bufhidden=delete
    setlocal syntax=man

    silent normal ggdG
    " Remove function/method arguments
    let s:name2 = substitute(a:name, '(.*', '', 'g' )
    " Remove all colons
    let s:name2 = substitute(s:name2, ':', '', 'g' )
    if a:type == 1
        execute  "silent read !" g:pydoc_cmd s:name2
    else
        execute  "silent read !" g:pydoc_cmd "-k" s:name2
    endif
    normal 1G

    if exists('l:pydoc_wh') && l:pydoc_wh != -1
        execute "silent resize" l:pydoc_wh
    end

    if g:pydoc_highlight == 1
        execute 'syntax match pydoc' "'" . s:name2 . "'"
    endif

    let l:line = getline(2)
    if l:line =~ "^no Python documentation found for.*$"
        if l:buf_is_new
            execute "bdelete!"
        else
            normal u
            setlocal nomodified
            setlocal nomodifiable
        endif
        redraw
        echohl WarningMsg | echo l:line | echohl None
    else
        setlocal nomodified
        setlocal nomodifiable
    endif
endfunction

" Mappings
function s:PerformMappings()
    nnoremap <silent> <buffer> <Leader>pw :call <SID>ShowPyDoc('<C-R><C-W>', 1)<CR>
    nnoremap <silent> <buffer> <Leader>pW :call <SID>ShowPyDoc('<C-R><C-A>', 1)<CR>
    nnoremap <silent> <buffer> <Leader>pk :call <SID>ShowPyDoc('<C-R><C-W>', 0)<CR>
    nnoremap <silent> <buffer> <Leader>pK :call <SID>ShowPyDoc('<C-R><C-A>', 0)<CR>

    " remap the K (or 'help') key
    nnoremap <silent> <buffer> K :call <SID>ShowPyDoc(expand("<cword>"), 1)<CR>
endfunction

if g:pydoc_perform_mappings
    call s:PerformMappings()
endif

" Commands
command -nargs=1 Pydoc       :call s:ShowPyDoc('<args>', 1)
command -nargs=* PydocSearch :call s:ShowPyDoc('<args>', 0)

Теги:
Хабы:
+26
Комментарии30

Публикации

Изменить настройки темы

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн