Obsah
1. Předávání parametrů funkcím
2. Parametry se při volání funkcí předávají hodnotou
3. Nepřímý přístup k proměnným přes jejich název reprezentovaný řetězcem
5. Použití funkce CreateForLoop()
6. Využití uživatelských funkcí v praxi – properties v C#
7. Přepis funkce CreateProperty() pro programovací jazyk Java
8. Vylepšení funkce pro automatické vytváření getterů a setterů
1. Předávání parametrů funkcím
V předchozích dvou částech seriálu o textovém editoru Vim jsme si popsali většinu důležitých vlastností interního skriptovacího jazyka nazvaného Vim Script. Zbývá nám ještě popsat, jakým způsobem se předávají parametry do volaných funkcí a jak lze zařídit předávání parametrů odkazem (i když v tomto případě se jedná spíše o určité zneužití vlastností Vim Scriptu). Začněme jednoduchým příkladem – funkcí, která očekává dva parametry, kterými by měla být dvě čísla (nebo řetězce převeditelné na čísla). Funkce obě čísla sečte a na pozici kurzoru v aktivním bufferu vloží výsledek tohoto součtu. Název funkce by měl začínat velkým písmenem, tj. například Add a nikoli add. Dále si povšimněte, že k parametrům funkce se přistupuje s využitím prefixu a: a že je možné vkládat nový obsah i do speciálního pracovního registru ", jehož název musí začínat prefixem @ – viz též předminulá část tohoto seriálu.
Posledním novým příkazem je execute s parametrem začínajícím na „normal“. Tento příkaz zajistí, že se další sekvence znaků provede v normálním režimu. Ona sekvence znaků se v našem případě rovná pouze jedinému znaku p, což v normálním režimu odpovídá editačnímu příkazu put. Pokud není explicitně specifikován žádný pracovní registr, umístí tento příkaz za pozici kurzoru obsah speciálního pracovního registru ", který jsme však naplnili součtem parametrů funkce. Podívejme se na zdrojový kód:
function! Add(parametr1, parametr2) " nejprve se provede soucet obou parametru let soucet = a:parametr1 + a:parametr2 " soucet se ulozi do specialniho pracovniho " registru " let @" = soucet " nasledne se pomoci prikazu execute " vykona editacni prikaz 'p' = put execute "normal p" endfunction
Obrázek 1: Obarvený zdrojový kód prvního demonstračního příkladu s funkcí Add().
Funkci lze otestovat následovně – samotná funkce se uloží do souboru pojmenovaného test_add.vim. Posléze se spustí Vim a zadá se příkaz:
:source test_add.vim
popř. pouze:
:so test_add.vim
který zajistí načtení funkce. Následně je možné v normálním režimu funkci zavolat příkazem:
:call Add(1,2)
Popř. lze použít i poněkud složitější test využívající znalostí, které jsme si ozřejmili v předchozí části tohoto seriálu:
for i in range(1,10) call Add(i,i) execute "normal o\<Esc> endfor
V předchozím testu je příkaz execute použit pouze pro odřádkování, aby se jednotlivá čísla nezapisovala přímo za sebou.
Obrázek 2: Spuštění demonstrační funkce Add() ve smyčce.
2. Parametry se při volání funkcí předávají hodnotou
Ve Vim Scriptu se všechny parametry předávají hodnotou, což znamená, že se před zavoláním nějaké funkce všechny její parametry nejprve vyhodnotí (může se jednat o výrazy obsahující i volání jiných funkcí) a teprve poté je funkce zavolána a jsou jí předány hodnoty vypočtených parametrů. V praxi to znamená to, že není možné, aby funkce měnila hodnoty proměnných, které jsou jí (alespoň zdánlivě) předány jako parametry; Vim Script jde dokonce ještě dále a neumožňuje vůbec měnit hodnoty proměnných začínajících prefixem a:, což je poměrně velký rozdíl oproti mnoha dalším programovacím jazykům. Tuto vlastnost Vim Scriptu si můžeme poměrně jednoduše vyzkoušet na následujícím demonstračním příkladu, který po svém spuštění pomocí příkazu:
:source TestVar.vim
Vypíše chybové hlášení při pokusu o změnu proměnné a:variable ve funkci ChangeValue. Následuje výpis zdrojového kódu tohoto příkladu. Povšimněte si, že se jedná o „samospouštěcí“ příklad, protože na svém konci obsahuje volání call TestVar():
function! PrintValue(variable) echo "variable=" . a:variable endfunction function! ChangeValue(variable) let a:variable = 6502 endfunction function! TestVar() let g:x = 42 call PrintValue(g:x) call ChangeValue(g:x) call PrintValue(g:x) endfunction call TestVar()
Obrázek 3: Zdrojový kód demonstrační funkce Add() a počítané programové smyčky, z níž se tato funkce volá.
3. Nepřímý přístup k proměnným přes jejich název reprezentovaný řetězcem
V některých případech by však bylo vhodné a užitečné umět změnit hodnotu proměnné ve funkci a přitom nepoužívat přímý přístup ke globálním proměnným. Vim Script sice tuto možnost přímo neobsahuje, ale je možné použít malý trik – pokud se ve zdrojovém kódu vyskytuje zápis:
{"jméno_proměnné"}
popř.
{proměnná_obsahující_jméno_proměnné_jako řetězec}
nebo taktéž:
{výraz,_který_se_vyhodnotí_na_řetězec_obsahující_jméno_proměnné}
… je na místo tohoto zápisu přímo dosazena proměnná daného jména. To znamená, že výše uvedený demonstrační program je možné upravit tak, aby se funkci ChangeValue předávalo jméno proměnné a nikoli její hodnota, tj. namísto g:x se použije zápis „g:x“:
function! PrintValue(variable) echo "variable=" . a:variable endfunction function! ChangeValue(variableName) let {a:variableName} = 6502 endfunction function! TestVar() let g:x = 42 call PrintValue(g:x) call ChangeValue("g:x") call PrintValue(g:x) endfunction call TestVar()
4. Využití uživatelských funkcí v praxi – vkládání připravených kusů programového kódu (code snippets) do editovaného textu
Interní skriptovací jazyk Vim Script je možné využít mnoha různými způsoby, jak pro psaní jednoduchých pomocných funkcí umisťovaných většinou do konfiguračního souboru .vimrc (což ovšem není nic jiného, než plnohodnotný skript a nikoli pouze jednoduchý soubor s konfigurací), tak i pro programování rozsáhlých přídavných modulů, mezi něž patří například i v příští části tohoto seriálu popsaný modul Netrw. V této kapitole si ukážeme, jak lze napsat poměrně jednoduchou funkci určenou pro vložení připraveného bloku programového kódu (code snippet) do editovaného souboru. Tato funkce bude sice velmi jednoduchá, ale jedná se o prazáklad složitějších a pro práci velmi užitečných plnohodnotných přídavných modulů, mezi něž patří především modul snipMate, jehož popisem se budeme zabývat v následující části tohoto seriálu. Naše ukázková funkce bude sloužit pro vložení zdrojového kódu představujícího počítanou programovou smyčku typu for zapsanou podle syntaktických pravidel céčka nebo Javy. Před vyvoláním této funkce musí být na aktivním řádku zapsána počáteční a koncová hodnota smyčky, tj. například dvě číslice, názvy konstant atd. oddělené od mezerou
Obrázek 4: Obarvený zdrojový kód funkce CreateForLoop().
Tělo funkce nazvané CreateForLoop vypadá následovně:
" velmi jednoduchy snippet - smycka for function! CreateForLoop() " presunout kurzor na zacatek pocatecni hodnoty pocitadla execute "normal bb" " prvni radek pocitane smycky for execute "normal ifor (int i = \<Esc>ea; i <\<Esc>ea; i++) {" " prazdne telo smycky execute "normal o\<Esc>" " konec tela a presunuti kurzoru do prikazoveho bloku execute "normal o}\<CR>\<CR>\<Esc>kkka " endfunction
Obrázek 5: Obsah textového souboru před zavoláním funkce CreateForLoop().
Povšimněte si především způsobu použití příkazu execute (zkracovaného taktéž na exe), který v podstatě simuluje příkazy zapisované přímo z klávesnice uživatelem. Za příkazem execute následuje řetězec, v němž je napsáno, ve kterém režimu (normální, vkládací…) má příkaz pracovat a poté již následuje text, který odpovídá sekvenci znaků zapisovaných na klávesnici. Jak je ze zápisu funkce patrné, je nutné některé znaky se speciálním významem uvozovat pomocí zpětného lomítka. Jedná se především o znak <, ale též o znak uvozovek (ten však není v příkladu použit).
Obrázek 6: Obsah textového souboru po zavolání funkce CreateForLoop().
5. Použití funkce CreateForLoop()
Výše uvedenou funkci CreateForLoop() je sice možné kdykoli zavolat pomocí následujícího příkazu zadávaného v příkazovém režimu, tj. s dvojtečkou na začátku:
:call CreateForLoop()<CR>a
ale to je samozřejmě příliš komplikované, a to i při použití tabulátoru pro doplnění celého jména funkce (dokonce bych řekl, že je to komplikovanější než samotný přímý zápis počítané smyčky).
Obrázek 7: Funkci CreateForLoop() lze použít i v případě, že jsou meze smyčky zadány pomocí jmen proměnných nebo konstant.
Z tohoto důvodu je většinou vhodnější využít mapování, například na kombinaci znaků ,f zapsaných ve vkládacím režimu. Zde se využívá především toho faktu, že za čárkou buď následuje ve zdrojových kódech číslice nebo mezera, nikoli písmeno (přesněji řečeno písmeno se může po čárce vyskytovat, programátoři však za tímto znakem většinou zapisují mezeru, neboť se tím zvyšuje čitelnost):
:imap ,f <Esc>:call CreateForLoop()<CR>a
Obrázek 8: Výsledek zavolání funkce CreateForLoop() pro dvojici konstant.
Mnoho uživatelů preferuje doplňování kódu klávesou Tab přímo ve vkládacím režimu, což je taktéž relativně snadné zařídit. Následující mapování zaručí, že po zápisu znaku f ihned za koncovou hodnotou smyčky a po stlačení klávesy Tab dojde k automatickému vložení snippetu:
:imap f<Tab> <Esc>:call CreateForLoop()<CR>a
Obrázek 9: Zobrazení menu s možností zavolání funkce CreateForLoop().
Pokud namísto vytváření nových mapování (které je nutné si zapamatovat nebo někam zapsat) preferujete volání funkcí z menu, je řešení poměrně snadné:
amenu 300.110 &Insert.&For\ loop<TAB>,f :call CreateForLoop()
S přesným významem předchozího příkazu se seznámíme příště.
Obrázek 10: Výsledek zavolání funkce.
6. Využití uživatelských funkcí v praxi – properties v C#
Funkce CreateForLoop uvedená v předchozích kapitolách byla poměrně jednoduchá. Zkusme si tedy vytvořit funkci nepatrně složitější a možná taktéž užitečnější. Programátoři vyvíjející aplikace v programovacích jazycích C#, C++ či Java velmi často přidávají do svých tříd atributy, k nimž vytváří takzvané settery a gettery, tj. metody určené pro nastavování resp. čtení hodnoty těchto atributů. Na této stránce je kromě dalších zajímavých informací uvedena i funkce, která umožňuje vytvoření setterů a getterů v programovacím jazyce C#, v němž je možné použít zkrácenou syntaxi zápisu. Originální – poněkud nečitelný – tvar funkce CreateProperty i s příslušným mapováním na klávesové zkratky (navržené autorem této funkce) je následující:
function! CreateProperty(type) execute "normal bim_\<Esc>b\"yywiprivate ".a:type." \<Esc>A;\<CR>public ".a:type. \ " \<Esc>\"ypb2xea\<CR>{\<Esc>oget\<CR>{\<CR>return " . \ "\<Esc>\"ypa;\<CR>}\<CR>set\<CR>{\<CR>\<Tab>\<Esc>\"yPa = value;\<CR>}\<CR>}\<CR>\<Esc>" normal! 12k2wi endfunction
Obrázek 11: Zdrojový kód původní varianty funkce CreateProperty() určené pro programovací jazyk C#.
imap <C-c><C-p><C-s> <Esc>:call CreateProperty("string")<CR>a imap <C-c><C-p><C-i> <Esc>:call CreateProperty("int")<CR>a
Obrázek 12: Ukázka kódu vytvořeného původní variantou funkce CreateProperty().
7. Přepis funkce CreateProperty() pro programovací jazyk Java
Zkusme si nyní funkci CreateProperty() z předchozí kapitoly v rámci procvičování Vim Scriptu upravit takovým způsobem, aby byla vhodná pro použití v programovacím jazyce Java, v němž se (alespoň prozatím!) žádná zkrácená syntaxe pro zápis setterů a getterů nepoužívá. Funkce při svém zavolání očekává, že jí bude předán textový parametr s názvem datového typu vytvářeného atributu (int, String, Color, Customer, FooBarClass atd.) a navíc se před kurzorem bude nacházet název atributu, ideálně začínající malým písmenem (to není nutnou podmínkou, jen to odpovídá štábní kultuře dodržované programátory v Javě). Funkce pracuje takovým způsobem, že nejprve jméno atributu uloží do pracovního registru x pomocí operátoru yw (yank word). Posléze změní první znak atributu na velké písmeno operátorem gU a atribut vymaže (!) do pracovního registru y. Zbylý kód již pouze správně vypíše typ atributu, jeho název i settery a gettery.
Obrázek 13: Funkce CreateProperty() upravená tak, aby byla použitelná v programovacím jazyku Java.
Následuje zdrojový kód „javovské“ varianty funkce CreateProperty(), která již obsahuje mnoho vysvětlujících komentářů:
" funkce, ktera pro slovo, na nemz se nachazi kurzor " vytvori property daneho typu function! CreateProperty(type) " ulozit slovo nachazejici se pod kurzorem " do registru 'x' execute "normal b\"xyw" " zmena prvniho znaku slova na velke pismeno " a ulozeni zmeneneho nazvu do registru 'y' " (slovo - nazev atributu - je vymazano!) execute "normal gUl\"ydw" " prvni radek - typ a jmeno property jako " privatniho atributu objektu execute "normal iprivate " . a:type . " \<Esc>\"xpa;\<CR>\<CR>" " vytvoreni getteru execute "normal ipublic " . a:type . " get\<Esc>\"ypa() {\<CR>" execute "normal i return this.\<Esc>\"xpa;\<CR>" execute "normal i}\<CR>\<CR>" " vytvoreni setteru execute "normal ipublic void set\<Esc>\"ypa(" . a:type . " value) {\<CR>" execute "normal i this.\<Esc>\"xpa=value;\<CR>" execute "normal i}\<CR>" endfunction
Obrázek 14: Ukázka programového kódu vytvořeného upravenou variantou funkce CreateProperty()
8. Vylepšení funkce pro automatické vytváření getterů a setterů
Funkci CreateProperty je opět možné volat přímo z příkazového režimu:
:call CreateProperty("String")
Ovšem mnohem lepší je použít vhodná mapování, například:
" cps = create property string map ,cps :call CreateProperty("String")<CR>
" cpi = create property integer map ,cpi :call CreateProperty("int")<CR>
" cpf = create property float map ,cpf :call CreateProperty("float")<CR>
" cpd = create property double map ,cpd :call CreateProperty("double")<CR>
Rychlejší však bude vytvoření pomocné funkce, která při svém zavolání (většinou opět s využitím mapování) očekává na editovaném řádku dvojici slov. Prvním slovem bude typ atributu, druhým slovem jeho název. Nově vytvořená funkce se bude jmenovat CreatePropertyWithType a bude se volat například přes následující mapování:
imap ,cp <Esc>:call CreatePropertyWithType()<CR>a
Obrázek 15: Funkce CreateProperty() upravená tak, aby byla použitelná v programovacím jazyku Java. Tato funkce může být volaná z další funkce CreatePropertyWithType(), která na editovaném programovém řádku očekává jak typ atributu, tak i jeho název.
Možná bude zajímavé se podívat na způsob, jakým je vyřešena „přeměna“ prvního zapsaného slova na parametr funkce CreateProperty. Ve skutečnosti to jde docela jednoduše, protože se toto slovo smaže pomocí příkazu bde (go to Begin of word, Delete to End), což mj. znamená, že se toto slovo uloží do pracovního registru „. Hodnota tohoto registru je přístupná přes pseudoproměnnou @“, jejíž hodnota je uložena (jen pro přehlednost) do proměnné propertyType a následně je tato proměnná předána jako parametr funkci CreateProperty:
function! CreatePropertyWithType() " prechod kurzoru na zacatek prvniho slova " a smazani celeho slova s ulozenim do registru " execute "normal bbde" " ziskani hodnoty registru " a nasledne ulozeni " do promenne propertyType let propertyType=@" " smazani mezery a presun na konec druheho slova execute "normal xe" " zavolani puvodni funkce CreateProperty call CreateProperty(propertyType) endfunction " funkce, ktera pro slovo, na nemz se nachazi kurzor " vytvori property daneho typu function! CreateProperty(type) " ulozit slovo nachazejici se pod kurzorem " do registru 'x' execute "normal b\"xyw" " zmena prvniho znaku slova na velke pismeno " a ulozeni zmeneneho nazvu do registru 'y' execute "normal gUl\"ydw" " prvni radek - typ a jmeno property jako " privatniho atributu objektu execute "normal iprivate " . a:type . " \<Esc>\"xpa;\<CR>\<CR>" " vytvoreni getteru execute "normal ipublic " . a:type . " get\<Esc>\"ypa() {\<CR>" execute "normal i return this.\<Esc>\"xpa;\<CR>" execute "normal i}\<CR>\<CR>" " vytvoreni setteru execute "normal ipublic void set\<Esc>\"ypa(" . a:type . " value) {\<CR>" execute "normal i this.\<Esc>\"xpa=value;\<CR>" execute "normal i}\<CR>" endfunction
Obrázek 16: Ukázka programového kódu vytvořeného upravenou variantou funkce CreateProperty() volané z funkce CreatePropertyWithType(). Nejprve byla ručně zapsána kostra třídy, posléze poloautomaticky vytvořeny atributy i s příslušnými settery a gettery a na závěr byl zdrojový kód zformátován pomocí příkazu =% (kurzor se musel nacházet na jedné ze složených závorek omezující třídu Customer). Čas vytvoření tohoto souboru: 15 sekund.
9. Odkazy na Internetu
- snipMate : TextMate-style snippets for Vim
http://www.vim.org/scripts/script.php?script_id=2540 - msanders / snipmate.vim
https://github.com/msanders/snipmate.vim - snipMate.vim Introductory Screencast
http://vimeo.com/3535418 - Clewn home page
http://clewn.sourceforge.net/ - How to connect vim with gdb – using clewn
http://chunhao.net/blog/how-to-connect-vim-with-gdb-using-clewn - yavdb : Yet Another (Generic) Vim Debugger Integration
http://www.vim.org/scripts/script.php?script_id=1954 - Vim home page
http://www.vim.org/ - Exuberant ctags
http://ctags.sourceforge.net/ - xxd (man page)
http://www.linux-tutorial.info/modules.php?name=ManPage&sec=1&manpage=xxd - vim (man page)
http://www.linux-tutorial.info/modules.php?name=ManPage&sec=1&manpage=vim - ctags (man page)
http://www.linux-tutorial.info/modules.php?name=ManPage&sec=1&manpage=ctags - cscope (man page)
http://www.linux-tutorial.info/modules.php?name=ManPage&sec=1&manpage=cscope - Tutorial: Make Vim as Your C/C++ IDE Using c.vim Plugin
http://www.thegeekstuff.com/2009/01/tutorial-make-vim-as-your-cc-ide-using-cvim-plugin/ - c.vim : C/C++ IDE
http://vim.sourceforge.net/scripts/script.php?script_id=213 - c.vim : C/C++ IDE key mappings
http://lug.fh-swf.de/vim/vim-c/c-hotkeys.pdf - Základní základy editoru Vim
http://www.root.cz/clanky/zakladni-zaklady-editoru-vim/ - Jak si přizpůsobit Vim
http://www.root.cz/serialy/jak-si-prizpusobit-vim/ - Novinky ve VIM 7: Úvodní část – speller
http://www.root.cz/vim-sedm-prvni-cast/ - Novinky ve VIM 7: Skriptovací jazyk
http://www.root.cz/vim-sedm-druha-cast/ - vim2elvis: Přednosti a nedostaky Elvise v porovnání s Vimem
http://www.root.cz/clanky/vim2elvis-1/ - vim2elvis: Shodné znaky mezi Elvisem a Vimem, nastaveníeditoru
http://www.root.cz/clanky/vim2elvis-2/ - Nej… VIM pluginy (1)
http://www.root.cz/clanky/nej-vim-pluginy/ - Taglist (plugin)
http://www.vim.org/scripts/script.php?script_id=273 - The NERD tree: A tree explorer plugin for navigating the filesystem
http://www.vim.org/scripts/script.php?script_id=1658 - JavaBrowser : Shows java file class, package in a tree as in IDEs. Java source browser.
http://www.vim.org/scripts/script.php?script_id=588 - snippetsEmu : An attempt to emulate TextMate's snippet expansion
http://www.vim.org/scripts/script.php?script_id=1318 - Scroll Lock (Necyklopedie)
http://necyklopedie.wikia.com/wiki/Scroll_lock - Caps Lock (Necyklopedie)
http://necyklopedie.wikia.com/wiki/Caps_Lock - Avoid the escape key
http://vim.wikia.com/wiki/Avoid_the_escape_key - Map caps lock to escape in XWindows
http://vim.wikia.com/wiki/VimTip166