powrót do strony głównej >>

Kilka porad do edytora Vim

Who on efficient work is bent,
Must choose the fittest instrument.
-- Faust, Part 1, Prologue for the Theatre

Uwaga: Niniejszą stronę z moimi poradami stworzyłem około roku 2004 (edytora Vim używam jeszcze dłużej). Wiele rzeczy przedstawionych tutaj może być już nieaktualnych. Od tamtego czasu odszedłem od wielu zaproponowanych koncepcji. Stwierdzam obecnie, że wiele propozycji było przesadnie złożonych. Odszedłem już od próby używania tego edytora jako narzędzia do wszystkiego oraz od nadmiernej rozbudowy konfiguracji. Używam obecnie prostej konfiguracji oraz podstawowych pluginów (pakiety Debiana: vim-scripts, vim-addon-manager).


Po pewnym czasie używania rozbudowanego edytora, jakim jest Vim, i dostosowywania go do własnych upodobań powstaje problem zamieszania, które wkrada się do coraz bardziej rozbudowanych plików konfiguracyjnych. Zaczynają się mieszać gotowe skrypty i wtyczki z naszymi własnymi modyfikacjami, łatwo zapomnieć, które skrypty mamy w najnowszych wersjach, a które w starych, ale zmienionych przez nas. Opiszę tu kilka wygodnych wskazówek dotyczących Vima, które być może już znasz.

Bardzo wygodnie jest rozdzielać swoje skrypty na osobne pliki zamiast trzymać wszystko w vimrc. Proponuję korzystać z dodatkowego katalogu, dodanego do 'runtimepath', w którym znajdować się będą tylko nasze własne skrypty pomocnicze. Czyli na wstępie stosujemy komendę:

set rtp+=~/.vim.own

Automatyczne pliki szablonów

W katalogu .vim.own/templates trzymam szablony dla różnych typów plików, które są automatycznie wstawiane w chwili tworzenia nowego pliku na podstawie jego rozszerzenia.

autocmd BufNewFile * sil! exe "read ~/.vim.own/templates/"
   \ . expand("<afile>:e") | 1d |
   \ sil! exe "source ~/.vim.own/templates/".expand("<afile>:e").".vim"

Ostatnia linijka powoduje dodatkowo próbę wykonania skryptu w zależności od rozszerzenia, który może służyć do uzupełnienia wstawionego szablonu. Przykładowo używając szablon dla plików nagłówkowych C, pozostaje do niego wstawić nazwy definicji preprocesora, które zależą od nazwy pliku nagłówkowego:

1
let s:nazwa =  toupper(expand("%:t:gs?\\.?_?"))
exe "norm A" . s:nazwa
norm j$h
exe "norm i" . s:nazwa
norm G
exe "norm A /* __HAVE_" . s:nazwa . " */"
norm 1G}j
		

Przyjazny schemat kolorów

Biorąc pod uwagę liczbę godzin spędzanych codziennie właśnie w edytorze, nie można dopuścić, by używany schemat kolorów był choć trochę nieczytelny lub sprawiał jakikolwiek dyskomfort. Żaden ze standardowych schematów nie odpowiadał mi całkowicie, dlatego ostatecznie po wielu zmianach przygotowałem własny schemat dla Vima. Jest prosty, niezbyt jaskrawy, ale zapewnia odpowiedni kontrast na różnych typach terminali (xterm, linux, putty, gvim), nie razi przy słabym oświetleniu. Zadziwiające, że wielu ludziom nie przeszkadza to, jak zupełnie nieczytelne są standardowe niebieskie komentarze np. w Putty. Mój schemat kolorów.
TODO: Od dawna mam pewien pomysł, którego nigdy nie zrealizowałem. Może natężenie jaskrawości schematu kolorów powinno się samo dostosowywać do aktualnej pory dnia, dnia roku, godziny wschodu i zachodu słońca oraz zachmurzenia na danych obszarze w aktualnym dniu, które byłoby pobierane przez sieć w momencie uruchomienia Vima? Jest zrozumiałe, że inne natężenie kolorów jest odpowiednie zimową nocą, a inne latem w bezchmurne południe.

Nieznoszę, gdy jakikolwiek program miga po oczach wizualnym dzwonkiem zamiast wydania dźwięku. Wolę nawet nie wiedzieć o beep-nięciu, niż dostać denerwujący błysk. A najlepiej beep powinien się wyświetlić w ostatniej linii ekranu, co można uzyskać używając programu screen(1). Visual bell zalecam wyłączyć.

set novisualbell

Powrót do miejsca zakończenia

Zawsze po wejściu do jakiegoś pliku powinniśmy znaleźć się w tym miejscu, w którym byliśmy podczas ostatniej wizyty w tym pliku. Dlatego warto dodać autokomendę używającą zakładkę '":

autocmd BufReadPost * if line("'\"") && line("'\"") <= line("$") |
   \ exe "normal `\"" | endif
		

Domyślne tworzenie pliku znaczników

Ctags to bardzo pomocne narzędzie do szybkiego poruszania się po dużym kodzie źródłowym. Jeśli nie chce nam się pamiętać o tym, by zawsze utworzyć plik tagów dla danego drzewa katalogów, proponuję skorzystać z funkcji, która będzie sama wywoływać utworzenie tego pliku w chwili, gdy chcemy się odwołać do jakiegoś znacznika.

set tags=./tags,tags;
nnoremap <C-]> :call <SID>Tag()<CR>\|:echo ""<CR>

func! s:Tag()
   try
      exe "norm! \<c-]>"
   catch /^Vim([^)]\+):E433/
      call inputsave()
      let l:kat = input("Katalog główny projektu:   ", ".")
      let l:opt = input("Opcje dla ctags:   ", "-R --fields=+S")
      call inputrestore()
      exe "!cd " . l:kat . " ; ctags " . l:opt . " ."
      exe "norm! \<c-]>"
   endtry
endfun
		

Szybkie przygotowanie kodu

Często się zdarza, że dostajemy czyjś kod źródłowy, który chcemy szybko obejrzeć. Ciągłe przetwarzanie kodu na wygodny dla nas format jest bardzo nużącą czynnością. Mam tu na myśli chociażby dosowe znaki końców linii i nieczytelnie zrobione wcięcia w kodzie (okropnie denerwujące). Z tego względu proponuję następujący skrypcik, który poprawia przynajmniej te rzeczy.

#!/bin/bash

echo Przetwarzanie kodu źródłowego...
shopt -s nullglob
for p in *.cpp *.h *.cxx *.c; do
   echo -e 'set ffs=dos,unix \n e \n set ff=unix \n wq' \
      | vim -e -- "$p" 2>&1 | egrep -v '^[^[:print:]]+$'
   echo -e '1\nnorm =G\nw' | vim -e -- "$p" 2>&1 | egrep -v '^[^[:print:]]+$'
done
		

Ponadto proponuję następujący mały, domyślny plik Makefile, jeśli przypadkiem nie przesłano nam również makefile projektu.

CXXFLAGS+=-g
CFLAGS+=-g

all: $(patsubst  %.cpp,%.o,$(wildcard *.cc *.cxx *.cpp *.C)) \
     $(patsubst  %.c,%.o,$(wildcard *.c))
   g++ -g $^

tags:
   ctags -R .

Uruchamianie edytowanego pliku

Z większością plików, które przeważnie edytujemy, można skojarzyć jedną czynność, która daje się z nimi wykonać. Przykładowo dla pliku źródłowego LISP intuicyjną czynnością jest wykonanie go przez interpreter, dla pliku zasobów X11 - wczytanie go, a dla skryptu Vima - uruchomienie go w aktualnej instancji edytora.

Dlatego do wykonywania tej specyficznej dla pliku czynności używam konsekwentnie klawisza F12. W celu zachowania porządku wygodnie jest trzymać skrypty uzależnione od rozpoznanego typu pliku w oddzielnych plikach w .vim.own/ftplugin.

vim.vimnnoremap <buffer> <F12> :source %<CR>
awk.vimnnoremap <buffer> <F12> :!gawk -f %<CR>
php.vimnnoremap <buffer> <F12> :!php -f %<CR>
xdefaults.vim   nnoremap <buffer> <F12> :!xrdb -load %<CR>
xmodmap.vimnnoremap <buffer> <F12> :!xmodmap %<CR>
python.vimnnoremap <buffer> <F12> :!python %<CR>
pov.vim nnoremap <buffer> <F12> :!povray +P +a0 +am0 +Q0 -j0 -r0 %<CR>
lisp.vimnnoremap <buffer> <F12> :!clisp %<CR>

Dla programów w asemblerze (~/.vim.own/ftplugin/asm.vim) naciśnięcie F12 może powodować wywołanie nasma, wytworzenie pliku wykonywalnego przez zlinkowanie oraz uruchomienie vgdb (u mnie jest to vim skompilowany z gdb) z punktem przerwania ustawionym na symbolu start.

nnoremap <buffer> <F12> :call <SID>Uruchom()<CR>
function! s:Uruchom()
   let l:cmd = "nasm -f elf -F stabs % ;"
   let l:cmd = cmd . "ld %:r.o -o a.out ;"

   let l:gdb = "vgdb -c 'call gdb(\"file a.out\")'"
   let l:gdb = gdb . " -c 'nnoremap <F2> "
   let l:gdb = gdb . ":call gdb(\"br start\")<CR>"
   let l:gdb = gdb . ":call gdb(\"run\")<CR>"
   let l:gdb = gdb . "'"

   let l:cmd = l:cmd . l:gdb
   exe "!" . l:cmd
endfun

Wygodne mapowania dla C++

Podczas programowania w C++ używam następującego zestawu mapowań i funkcji pomocniczych. To jest wystarczające, by znacznie sobie przyśpieszyć wpisywanie tego, co zajmuje najwięcej czasu. Zgodnie z poprzednim paragrafem, klawiszowi <F12> jest przypisane zadanie zbudowania projektu, jeśli w katalogu występuje Makefile. W przeciwnym razie jest kompilowany aktualny plik.

Używanie iostream przy wypisywaniu lub pobieraniu tekstu wymaga dość dużo napracowania się palcami (za dużo << i shift trzeba wciskać!). Z tego względu najczęstszym użyciom cout, cin mam przypisane szybkie skróty, a wpisanie jednej z instrukcji sterujących samoczynnie wstawia nawiasy.

nnoremap <buffer> <F12> :call <SID>Kompiluj()<CR>
nnoremap <buffer> <S-F12> :exe "!" . b:cpptmp<CR>

inoremap <buffer> <F10>c cout <<  << endl;<c-o>8h
inoremap <buffer> <F10>C cin >><Space>;<Left>
inoremap <buffer> <F10>v <Space><<<Space>
inoremap <buffer> <F10>e <Space><< endl
inoremap <buffer> <F10>E <c-o>:set nohls<CR><c-o>:s/\s*;\s*$//e<CR>
         \<End><CR><<  << endl;<C-o>8h
inoremap <buffer> <F10>i #include <><Left>
inoremap <buffer> <F10>G cin.get();<CR>cin.get();

inoremap <silent><buffer> { <C-o>:call <SID>BlOpen()<CR>

iabbrev <silent><buffer> for for (; ; )<c-o>5h<C-R>=<SID>Eatchar('\s')<CR>
iabbrev <silent><buffer> while while ()<Left><C-R>=<SID>Eatchar('\s')<CR>
iabbrev <silent><buffer> if if ()<Left><C-R>=<SID>Eatchar('\s')<CR>
iabbrev <silent><buffer> switch switch ()<Left><C-R>=<SID>Eatchar('\s')<CR>

func! s:Eatchar(pat)
   let c = nr2char(getchar())
   return (c =~ a:pat) ? '' : c
endfunc

func! s:BlOpen()
   if getline('.') == ''
      exe "norm a\<C-V>{\<CR>}\<Esc>O \<bs>"
   elseif col('.') == col('$') - 1
      exe "norm a \<C-V>{\<CR>}\<Esc>O \<bs>"
   else
      exe "norm i\<C-V>{\<Esc>l"
   endif
endfunction

func! s:Kompiluj()
   if filereadable("Makefile")
      make!
      copen
      wincmd p
      return
   endif
   if !exists("b:cpparg")
      let b:cpparg = input("Argumenty dla kompilatora: ", "-Wall")
   endif
   if !exists("b:prgarg")
      let b:prgarg = input("Argumenty dla programu: ")
   endif
   let b:cpptmp = tempname()
   let &makeprg = "g++ -o " . b:cpptmp . " % " . b:cpparg
   make!
   copen
   wincmd p
   execute "!" . b:cpptmp . " " . b:prgarg
endfun

Szybsza obsługa dużych plików

Jeśli chcemy, by duże pliki otwierały się szybciej, zastosujmy następującą autokomendę. Spowoduje to pominięcie rozpoznawania typu pliku i nieużywanie pliku swap.

let g:DuzyPlik = 2 "MB
let g:DuzyPlik = g:DuzyPlik * 1024 * 1024
augroup DuzyPlik
   autocmd BufReadPre * let f=expand("<afile>") |
      \ if getfsize(f) > g:DuzyPlik | set eventignore+=FileType |
      \ setlocal noswapfile bufhidden=unload buftype=nowrite undolevels=-1 |
      \ else | set eventignore-=FileType | endif
augroup END

Integracja z The GNU Debuggerem

Dostępnych jest wiele rozwiązań pozwalających połączyć doskonały debugger GDB z Vimem. Przetestowałem chyba wszystkie i najbardziej mi przypadł do gustu VIMGdb. Wymaga modyfikacji kodu źródłowego Vima, a dostępne łaty są tylko do wersji 6.2, więc proponuję trzymać tak zmodyfikowaną wersję obok głównej, np. pod nazwą vimgdb. Modyfikacja daje nawet więcej, niż Emacs zintegrowany z debuggerem i działa bardzo sprawnie! Zawsze mi brakowało w GDB możliwości elastycznego oskryptowania go, podświetlania, a zwłaszcza podpięcia akcji pod klawisze funkcyjne. Odpluskiwanie jest procesem, w którym łatwo można wyodrębnić wiele powtarzanych czynności. Vimgdb sprawdza się do tego znakomicie. Podczas uruchamiania debugowanego programu wygodnie jest przekierować jego standardowe deskryptory na osobny terminal sterujący. Zrzut ekranu z VIMGdb.

Tworzenie planów i zarysów (outlining)

Wraz ze wzrostem złożoności czegokolwiek, z czym mamy do czynienia, szybko rośnie potrzeba solidnego i przemyślanego planowania. Napisanie skryptu może wymagać zaplanowania, nauka do egzaminu może wymagać zaplanowania, sprzątanie pokoju może wymagać solidnego planowania! Kolejność paragrafów na tej stronie również wymagała zaplanowania. Program, służący do tworzenia przejrzystych spisów, zarysów, szkiców i planów jest nazywany outliner. Istnieje sporo programów (jak klasyczny GrandView) do szybkiego notowania swoich pomysłów i myśli (brainstorming), zwłaszcza na komputery typu palmtop.

Jednym ze sposobów zapisu pomysłu lub zarysu jest przedstawienie go w postaci drzewa. Gałęzie grupują elementy z różnych poziomów szczegółowości. Problem jest przedstawiany przez podział na elementy na zasadzie dekompozycji. Dobry outliner powinien pozwalać na bardzo szybki dostęp do takich operacji jak zmiana hierarchii, kolejności gałęzi, promowanie zagnieżdzonych węzłów i łatwe zwijanie poddrzew. Powinien po prostu pozwalać łatwo zanotować pomysł, który jest w Twojej głowie.

Mechanizm zawijania, który pojawił się w wersji 6 Vima, umożliwił powstanie skryptu vimoutliner, dodającego Vimowi tryb outliningu. Istnieją dwa niezależne, ale zupełnie podobne, skrypty o nazwie vimoutliner. Używam tego o aktulanym numerze wersji 1.6. Outlining za pomocą Vima jest bardzo wydajny.

Jeszcze o skrótach klawiaturowych...

Bardzo lubię programy, którymi da się łatwo sterować jedynie za pomocą klawiatury i sporo się zastanawiałem o najbardziej efektywnym sposobie jej wykorzystania. Z tego powodu mam też zawsze mnóstwo dodatkowych czynności przypisanych pod pojedynczymi kombinacjami. Niestety najbardziej pożądane rozwiązania są niewykonalne z powodu technicznych ograniczeń klawiatury lub braku elastyczności X11. Klawiatura nie pozwala wykryć jednoczesnego wciśnięcia więcej niż kilku klawiszy, a X11 i konsola Linuksa pozwalają definiować tylko kilka podstawowych modyfikatorów. Zmieniłem kody kontrolne, wysyłane przez dodatkowe klawisze, które mam na klawiaturze. Przykładowo klawisz oznaczony księżycem wysyła kod ^[[R40, dzięki temu mogę używać tego klawisza do zamknięcia wszystkich buforów i wyjścia z edytora.

nnoremap <Esc>[R40 :qa!<CR>

Jeszcze nie jestem przekonany, do czego używać klawiszy strzałek na klawiaturze, bo w zasadzie nie nadają się do czegokolwiek. Aktualnie mam do nich podpięte na stałe ściszanie i podgłaśnianie dźwięku.

Używanie dwóch klawiatur?

Zdarzyło mi się raz spróbować sterowania Vimem za pomocą dwóch klawiatur. Jednej używałem jako standardowej, a klawiszom na drugiej próbowałem przypisać najczęściej powtarzane czynności. Przykładowo: naciśnięcie [c] na drugiej klawiaturze może wstawiać blok cout, [f] wstawiać szablon definicji funkcji itp. Jak skonfigurować coś takiego? Druga klawiatury była podłączona do drugiego komputera z klientem zdalnej powłoki połączonym z pierwszym komputerem, na którym uruchamiałem Vima w trybie serwera. Druga instancja edytora używała skryptu, odpowiedzialnego za odbieranie komend z drugiej klawiatury. Taki skrypt może wyglądać następująco. Ostatecznie pozostałem przy tradycyjnym używaniu jednej klawiatury.

nnoremap <Esc> :q
nnoremap u :Send "using namespace std;\n"<CR>
nnoremap r :Send "return ;<"."Left>"<CR>
nnoremap m :Send "int main(int argc, char * const *argv)\n" .
   \ "{\n\n" .
   \ "return 0;\n" .
   \ "}\n" .
   \ "\ekkkO\n"<CR>
nnoremap c :Send "cout <<  << endl;<"."c-o>8h"<CR>

command! -nargs=1 Send :call <SID>f_Wyslij(<args>)

func! s:f_Wyslij(komenda)
   call remote_send("gvim", a:komenda)
endfu

Pliki tekstowe z kodowaniem UTF-8 i ISO-8859-2

Jeśli z jakiegoś powodu nie chcesz (tak jak ja) przestawiać całego systemu na kodowanie UTF-8, ale chcesz edytować w Vimie zarówno pliki w kodowaniu UTF-8 i ISO-8859-2, to skorzystaj z następującego ustawienia:

if has("multi_byte")
  set enc=iso-8859-2 tenc=iso-8859-2 fencs=utf8,iso-8859-2
endif
Zmienna 'enc' oznacza kodowanie, którego Vim ma używać wewnętrznie do reprezentowania i przetwarzania łańcuchów tekstowych. 'tenc' to kodowanie używane przez terminal, w którym uruchomiono Vima. Jeśli nie mamy UTF-8, to ustawiamy to na iso8859-2. Ustawienie 'fenc' na utf8,iso-8859-2 powoduje, że przy otwieraniu plików Vim będzie próbował najpierw otworzyć plik w kodowaniu utf8, a jeśli to się nie uda, to użyje iso-8859-2. W powyższym ustawieniu nowe pliki będą kodowane w iso-8859-2. Jeśli domyślnie nowe pliki chcesz kodować w UTF-8, to zmień powyżej 'enc' na utf8.

Dodatkowe pluginy

Spośród setek skryptów i rozszerzeń dla Vima, dla mnie najbardziej przydatne są:




Pliki do pobrania
Nazwa Data Opis
robcolors.vim 2004-10-21 Mój czytelny schemat kolorów
vimrc 2004-10-21 Mój główny plik konfiguracyjny

Copyright © Robert Nowotniak
21 października 2004