Asynchronní skripty: další přiblížení Vimu k možnostem IDE

16. 10. 2018
Doba čtení: 24 minut

Sdílet

Prakticky přesně před dvěma roky vyšla osmá verze editoru Vim. Jde o přelomovou událost, protože právě Vim 8 umožňuje tvorbu asynchronně běžících skriptů, které tak mohou přiblížit možnosti Vimu plnohodnotným IDE.

Obsah

1. Pluginy využívající asynchronní volání ve Vimu

2. Podpora pro práci s datovým formátem JSON

3. Anonymní funkce

4. Reference na funkce

5. Časovače: cesta k asynchronnímu kódu

6. Vytvoření a použití několika časovačů

7. Kanály (channels)

8. Vytvoření kanálu a přenos dat

9. Úlohy (jobs)

10. Využití synergie mezi všemi zmíněnými technologiemi: skutečně fungující asynchronně běžící skripty

11. Plugin asyncrun

12. Instalace pluginu asyncrun

13. Použití pluginu asyncrun

14. Plugin agrep

15. Použití pluginu agrep

16. Plugin amake

17. Použití pluginu amake

18. Okno terminálu (novinka ve Vimu 8.1)

19. Odkazy na Internetu

1. Pluginy využívající asynchronní volání ve Vimu

Textový editor Vim, s nímž se na stránkách Rootu setkáváme poměrně pravidelně, je mezi mnoha uživateli oblíbený jak díky svému modálnímu způsobu ovládání, tak i kvůli tomu, že je ho možné nakonfigurovat přesně takovým způsobem, aby plně vyhovoval všem uživatelským požadavkům. Ovšem kromě toho nesmíme zapomenout ani na fakt, že pro Vim existuje velké množství modulů (pluginů), které možnosti tohoto editoru dále rozšiřují. Moduly lze naprogramovat jak v jazyku Vim Script (ten se prakticky objevil až v páté verzi), tak i například v Pythonu, Perlu či v programovacím jazyku Lua.

Obrázek 1: Jedna z prvních verzí Vimu 8.0 (tehdy ještě nebyla zařazena do prakticky žádné stabilní distribuce).

I přesto, že je dnes možné ve Vimu naprogramovat i velmi složité moduly, byly jejich možnosti v porovnání s plnohodnotnými IDE v jedné oblasti omezené – všechny skripty běžely (velmi zjednodušeně řečeno) ve stejném vláknu jako zbytek textového editoru. To v praxi znamenalo, že například při spuštění překladače musel uživatel čekat na dokončení překladu a teprve poté bylo možné pokračovat v editaci, popř. ve zkoumání chyb vypsaných během překladu do tzv. Quickfix window (okno s rychlým náhledem). Podobně tomu bylo i u dalších operací (vyhledávání, volání LSP atd. atd.). Situace se ovšem zásadně změnila právě ve Vimu 8, který přinesl hned několik nových technologií využitelných právě pro tvorbu mnohem lepších pluginů. Dnes se s těmito technologiemi postupně seznámíme a ukážeme si jejich použití na trojici prakticky využitelných pluginů nazvaných asyncrun, agrep a amake.

Obrázek 2: Konfigurační volby, s nimiž byl přeložen Vim 8.0.

Poznámka: ve skutečnosti byly některé nové vlastnosti backportovány i do Vimu 7.4.x, ovšem přechod na Vim 8 je pro naprostou většinu uživatelů zcela bezproblémový.

2. Podpora pro práci s datovým formátem JSON

Na první pohled sice možná ne příliš výraznou, ale o to důležitější a užitečnější, novinkou je přidání čtyř nových funkcí do skriptovacího engine Vimu. Tyto čtyři funkce slouží pro převod datových struktur Vim Scriptu do formátu JSON a zpět. Proč je vlastně tato novinka tak důležitá? Souvisí totiž s další novou technologií, konkrétně s úlohami (jobs) a kanály (channels). Úlohy umožňují přesně definovaným způsobem vytvářet pluginy (i externí pluginy) s tím, že tyto pluginy mohou běžet asynchronně, tj. částečně nezávisle na samotném Vimu. Důležité je, že pluginy s Vimem komunikují právě přes JSON formát, což je pěkně navržené řešení, protože JSON je dnes podporovaný v prakticky všech relevantních programovacích jazycích, v nichž se externí pluginy většinou píšou (typicky se jedná o Python, ale lze použít i jazyk Lua apod.).

Obrázek 3: Nejdůležitější novinka ve Vimu 8 – příkaz :smile.

Poznámka: na existenci těchto funkcí je ostatně založen i nedávno popsaný plugin vim-lsp.

Obrázek 4: Příklad použití pluginu vim-lsp.

Skriptovací engine Vimu nabízí programátorům dvě funkce určené pro převod datových struktur do formátu JSON (encode) a dvě funkce určené pro parsování JSON dat do interních struktur Vim Scriptu (decode). Dvě varianty jsou implementovány z toho důvodu, že v některých případech je zapotřebí, aby byly klíče objektů (či slovníků) reprezentovány řetězci, a jinde se může jednat o identifikátory bez uvozovek (záleží na konkrétní situaci):

Funkce Stručný popis
json_encode(výraz) převod výrazu do JSON formátu, který se vrátí ve formě řetězce
json_decode(řetězec) opak předchozí funkce, parsování řetězce s daty uloženými v JSON formátu do interních datových struktur Vim Scriptu
js_encode(výraz) podobné funkci json_encode(), ovšem klíče nejsou umístěny v uvozovkách
js_decode(řetězec) podobné funkci json_decode(), ovšem při parsingu se nevyžaduje, aby byly klíče umístěny v uvozovkách
Poznámka: převod do JSON formátu se někdy nazývá serializace, zpětný převod pak pochopitelně deserializace.

Obrázek 5: Nápověda k funkcím js_encode a js_decode.

Ve Vim Scriptu existuje celkem deset datových typů, přičemž pět z těchto typů do jisté míry koresponduje s typy používanými ve formátu JSON (Number, Float, String, List a Dictionary) a dva další typy (Boolean, None) lze taktéž do formátu JSON převést, pokud se ovšem dodrží určitá pravidla. Výše zmíněné čtyři funkce json_encode(), json_decode(), js_encode() a js_decode() provádí převody mezi jednotlivými typy (resp. mezi hodnotami různých typů) zcela automaticky, ovšem existuje několik potenciálně problematických výjimek, kterými se budeme zabývat v navazujícím textu.

Obrázek 6: Serializace základních hodnot do JSONu.

V následující tabulce jsou vypsány korespondující datové typy. Povšimněte si, že v JSON formátu se nerozlišuje mezi celými čísly a čísly s plovoucí řádovou čárkou, což do značné míry souvisí s původním návrhem tohoto formátu založeném na JavaScriptu:

Typ ve Vim Scriptu Převod do JSONu
Number celé číslo
Float číslo s desetinnou čárkou a/nebo exponentem
String řetězec umístěný v uvozovkách
List pole hodnot
Dictionary objekt (asociativní pole)
Funcref nelze (teoreticky by se jednalo o serializaci kódu)
Boolean True/False
None Null
Job nelze
Channel nelze

Převody mezi skalárními typy jsou ve většině běžných případů triviální, proto se podívejme, jakým způsobem je možné v JSON formátu reprezentovat složené datové typy Vim Scriptu. Prvním typem, s nímž se setká prakticky každý programátor používající Vim Script, je seznam (list), který je ovšem někdy taktéž nazýván vektorem (vector) či polem (array), což ale není zcela přesné. V následujícím příkladu je do JSON formátu převeden seznam čísel, dále seznam řetězců a dvě matice reprezentované seznamem seznamů. Povšimněte si, že ve Vim Scriptu je nutné při zápisu výrazu či příkazu na více řádků použít zpětné lomítko na začátku (nikoli na konci!) řádku, což je dosti neobvyklé:

let vector1 = [1, 2, 3]
echo json_encode(vector1)
 
let vector2 = ["Hello", "world", "!"]
echo json_encode(vector2)
 
let matrix1 = [[1,2,3], [4,5,6], [7,8,9]]
 
echo json_encode(matrix1)
 
let matrix2 = [[1,2,3],
\             [4,5,6],
\             [7,8,9]]
 
echo json_encode(matrix2)

Obrázek 7: Výsledek spuštění předchozího skriptu s funkcí json_encode.

Samozřejmě můžeme namísto funkce json_encode() použít funkci js_encode(), která ovšem vytvoří shodný výsledek (prozatím totiž nepracujeme se slovníky):

let vector1 = [1, 2, 3]
echo js_encode(vector1)
 
let vector2 = ["Hello", "world", "!"]
echo js_encode(vector2)
 
let matrix1 = [[1,2,3], [4,5,6], [7,8,9]]
 
echo js_encode(matrix1)
 
let matrix2 = [[1,2,3],
\             [4,5,6],
\             [7,8,9]]
 
echo js_encode(matrix2)

Obrázek 8: Výsledek spuštění předchozího skriptu s funkcí js_encode.

Nejsložitější datovou strukturou, kterou je možné do JSON formátu uložit (serializovat), jsou takzvané slovníky (dictionary, dict), které jsou v jiných programovacích jazycích nazývány hashe či asociativní pole. Právě u slovníků se projeví rozdíl mezi funkcemi json_encode() a js_encode(), a to konkrétně při serializaci klíčů. Rozdíly jsou shrnuty v následující tabulce:

Typ klíče json_encode() js_encode()
řetězec odpovídající názvu proměnné v JS klíč je zapsán do uvozovek klíč je zapsán bez uvozovek
jiný řetězec (s mezerou, pomlčkou…) klíč je zapsán do uvozovek klíč je zapsán do uvozovek
číslo klíč je zapsán do uvozovek klíč je zapsán do uvozovek
jiný typ (seznam…) nelze použít nelze použít

Název proměnné v JavaScriptu musí začínat písmenem, podtržítkem či znakem dolaru a musí obsahovat čísla, písmena (nikoli jen ASCII, možnosti jsou zde větší), dolary či podtržítka.

V následujícím příkladu je ukázáno, jak je možné do JSON formátu ukládat slovníky, slovníky se seznamy (hodnoty) či naopak seznam slovníků:

let dict1 = {"first": 1, "second" : 2, "third" : 3}
echo json_encode(dict1)
 
let dict2 = {1 : "first", 2 : "second", 3 : "third"}
echo json_encode(dict2)
 
let dict3 = {"first" : [1,2,3], "second" : [4,5,6]}
echo json_encode(dict3)
 
let vectorOfDicts = [ {"first" : 1, "second" : 2}, {"another" : "dictionary"}]
echo json_encode(vectorOfDicts)

Obrázek 9: Výsledek spuštění předchozího skriptu s funkcí json_encode.

Pokud budeme shodné datové struktury serializovat funkcí js_encode(), budou se klíče ukládat rozdílným způsobem, ale pouze pro ty řetězce, které odpovídají jménům proměnných v JavaScriptu:

let dict1 = {"first": 1, "second" : 2, "third" : 3}
echo js_encode(dict1)
 
let dict2 = {1 : "first", 2 : "second", 3 : "third"}
echo js_encode(dict2)
 
let dict3 = {"first" : [1,2,3], "second" : [4,5,6]}
echo js_encode(dict3)
 
let vectorOfDicts = [ {"first" : 1, "second" : 2}, {"another" : "dictionary"}]
echo js_encode(vectorOfDicts)

Obrázek 10: Výsledek spuštění předchozího skriptu s funkcí js_encode.

3. Anonymní funkce

Poměrně významnou novinkou Vimu 8, kterou nalezneme ve skriptovacím jazyku Vim Script, jsou takzvané anonymní funkce (někdy se setkáme s možná přiléhavějším názvem lambdy, což je označení vycházející z lambda kalkulu). Anonymní funkce je možné použít ve všech místech programového kódu, kde jsou očekávány takzvané reference na funkce (funcrefs, viz další kapitolu). Zápis anonymní funkce se v několika ohledech odlišuje od zápisu běžné funkce. Parametry i samotné tělo funkce jsou totiž umístěny do složených závorek {} a mezi parametry a tělem funkce se zapisuje dvojice znaků → (u parametrů se navíc nemusí používat prefix a:, který je u běžných funkcí vyžadován). Velmi jednoduchou anonymní funkci, která po svém zavolání vrátí součet svých dvou parametrů, lze zapsat takto:

{x,y -> x + y}

Obrázek 11: Nápověda k lambda výrazům (anonymním funkcím).

Volání takové funkce může vypadat například takto:

:echo {x,y -> x + y}(1,2)

nebo (díky přetíženému operátoru +):

:echo {x,y -> x + y}([1,2,3], [4,5,6])

popř: (výsledek vás může překvapit)

:echo {x,y -> x + y}("1", "2")

Obrázek 12: Zavolání anonymní funkce.

Obrázek 13: Výsledek zavolání anonymní funkce.

Poznámka: ve skutečnosti není dvojtečka na začátku nutná ve chvíli, kdy se funkce volá ze skriptu (tedy typicky ze souboru s koncovkou .vim). Ovšem s dvojtečkou si můžeme volání funkce otestovat přímo z příkazového řádku Vimu.

Alternativně lze anonymní funkci přiřadit do proměnné (a tak ji vlastně pojmenovat):

:let Fce={x,y -> x + y}
:echo Fce(1,2)
3
Poznámka: samotná výhoda existence anonymních funkcí se ukáže až ve chvíli, kdy se seznámíme s dalšími možnostmi Vim Scriptu.

4. Reference na funkce

V předchozí kapitole jsme se – prozatím bez dalšího hlubšího vysvětlení – setkali s termínem „reference na funkci“ což je název běžně zkracovaný na „funcref“. Nejedná se sice o nový koncept zavedený až ve Vimu 8, ale vzhledem k tomu, že se s referencemi na funkce setkáme i v několika dalších demonstračních příkladech, si stručně vysvětlíme, o co se vlastně jedná. Reference na funkci obsahuje, což asi není příliš překvapivé, odkaz na existující funkci (přičemž nás nezajímá, jak konkrétně je tento odkaz reprezentován) a lze ji považovat za plnohodnotný datový typ skriptovacího jazyka Vim Script. To mj. znamená, že reference na funkce lze ukládat do proměnných (globálních i lokálních) či je předávat do jiných funkcí (takzvaných funkcí vyššího řádu). Reference na existující funkci se získá zavoláním function(jméno_funkce), tedy například následujícím způsobem:

function! DoubleValue(index, value)
    return a:value * 2
endfunction
 
let Funcref = function("DoubleValue")
Poznámka: povšimněte si, že se reference na funkci odlišuje od jména funkce, zatímco v mnoha jiných programovacích jazycích se jedná o totožný objekt (pokud daný jazyk podobné konstrukce vůbec umožňuje). Zde se ukazuje určitá nekoncepčnost Vim Scriptu, který byl původně mnohem jednodušším jazykem v porovnání s jeho současnou verzí.

Obrázek 14: Nápověda k datovému typu reference na funkce.

5. Časovače: cesta k asynchronnímu kódu

S anonymními funkcemi a referencemi na funkce (funcrefy) do jisté míry souvisí i další nová a velmi užitečná vlastnost Vimu 8. Jedná se o takzvané časovače (timers), které umožňují specifikovat, v jakém okamžiku se zavolá nějaká běžná či anonymní funkce. Důležité je, že tyto funkce jsou spouštěny asynchronně, tj. nezávisle na dalším běhu skriptu (a nezávisle na tom, jaké operace provádí uživatel!). Navíc je možné jednoduše specifikovat, kolikrát se má daná funkce zavolat, popř. je možné zajistit, aby se funkce volala periodicky až do ukončení běhu Vimu (to může být velmi užitečné, například u složitějších pluginů). Nový časovač se ve Vim Scriptu vytvoří zavoláním funkce nazvané příhodně timer_start(). Této funkci je nutné předat minimálně dva parametry: časový interval specifikovaný v milisekundách a anonymní funkci či funcref, která se má po uplynutí daného intervalu zavolat:

timer_start(interval, funcref či anonymní funkce)

Taktéž je možné do funkce timer_start() předat třetí nepovinný parametr, kterým je slovník obsahující další upřesnění funkce časovače. V současnosti je podporována jen jediná vlastnost – počet opakování (repeat) popř. specifikace periodického opakování volání funkce:

timer_start(interval, funcref či anonymní funkce, {'repeat':počet_opakování})

Periodické opakování používá magickou konstantu –1:

timer_start(interval, funcref či anonymní funkce, {'repeat':-1})

Funkce timer_start vrátí jednoznačný identifikátor právě vytvořeného časovače.

Příkladem použití může být snaha o zobrazení času na pravítku popř. na stavovém řádku. Pokus o pouhou změnu formátu pravítka ovšem nestačí, protože pravítko bude aktualizováno pouze tehdy, pokud uživatel bude provádět nějakou činnost (zjednodušeně: pokud bude mačkat klávesy):

set laststatus=2
set ruler
set rulerformat=%55(%{strftime('%a\ %b\ %e\ %I:%M:%S\ %p')}\ %5l,%-6(%c%V%)\ %P%)

Díky existenci časovače je ovšem změna obsahu pravítka například každou sekundu snadná:

set laststatus=2
set ruler
set rulerformat=%55(%{strftime('%a\ %b\ %e\ %I:%M:%S\ %p')}\ %5l,%-6(%c%V%)\ %P%)
 
function! UpdateStatusBar(timer)
    execute 'let &ro=&ro'
endfunction
 
let timer = timer_start(1000, 'UpdateStatusBar',{'repeat':-1})
Poznámka: zde jsme použili malého triku popsaného v nápovědě Vimu – pokud budeme chtít změnit obsah pravítka, postačuje změnit jakoukoli konfigurační volbu.

6. Vytvoření a použití několika časovačů

O tom, že je základní použití časovačů vlastně velmi jednoduché, se lze snadno přesvědčit prozkoumáním následujícího demonstračního příkladu, v němž jsou vytvořeny čtyři časovače volající uživatelskou funkci nazvanou PrintMessage. První časovač zavolá tuto funkci celkem šestkrát s periodou jedné sekundy, druhý časovač jedenkrát po 3,3 sekundách atd. Po spuštění tohoto skriptu sledujte zprávy vypisované do levého spodního rohu editoru:

" ---------------------------------------------
" Vim8 example script - timers
"
" How to use it:
" 1) start new Vim session
" 2) open this script in it
" 3) call :source %
" ---------------------------------------------
 
function! PrintMessage(message)
    echo a:message
endfunction
 
call PrintMessage("normal call")
 
let timer1 = timer_start(1000, 'PrintMessage', {'repeat':6})
echo "timer" timer1 "created"
 
let timer2 = timer_start(3300, 'PrintMessage')
echo "timer" timer2 "created"
 
let timer3 = timer_start(4400, 'PrintMessage')
echo "timer" timer3 "created"
 
let timer4 = timer_start(5500, 'PrintMessage')
echo "timer" timer4 "created"

7. Kanály (channels)

Zcela novou technologií, s níž se můžeme setkat ve Vimu 8, je koncept takzvaných kanálů neboli channels. Kanály primárně slouží ke komunikaci mezi Vimem (resp. přesněji řečeno mezi Vim Scriptem) a externími pluginy, které jsou spuštěny v samostatném procesu. Textový editor Vim s těmito pluginy komunikuje buď s využitím socketů nebo roury (pipe). Pro přenos dat se typicky používá výše zmíněný formát JSON a proto jsou tak užitečné ty funkce, které jsme si popsali v předchozích kapitolách. Ve skutečnosti se mohou používat čtyři typy zpráv. Tyto typy jsou pojmenovány RAW, NL, JSON a JS, viz též následující tabulku:

Typ zprávy Význam
RAW obecný formát, s nímž Vim nijak speciálně nenakládá, pouze zprávu pošle či přijme
NL každá textová zpráva je ukončena znakem pro konec řádku (NL)
JSON formát JSON
JS formát JSON s klíči odpovídajícími JavaScriptu

Samotné pluginy mohou pracovat ve čtyřech režimech:

Režim Typ připojení Popis
démon socket proces, ke kterému se může připojit více instancí Vimu
úloha (job) socket nebo pipe proces, ke kterému se připojuje jedna instance Vimu (ten ho typicky i spustí)
krátká úloha socket nebo pipe dtto jako předchozí, typicky však proces neběží po celou dobu existence Vimu
filtr pipe spouští se synchronně, Vim čeká na výsledek operace

8. Vytvoření kanálu a přenos dat

Nový kanál určený pro komunikaci s démonem či úlohou (viz tabulka uvedené v předchozí kapitole), se vytváří funkcí ch_open(). Například pro démona lokálně běžícího na stejném počítači a poslouchajícího na portu 1234 se použije tento příkaz:

let channel1 = ch_open("localhost:1234")
Poznámka: používejte čísla portů větší než 1024 a samozřejmě neobsazená dalšími službami. Obsazené porty vypíše například nástroj lsof.

Jakmile je kanál vytvořen, lze démonu poslat nějaká data, a to konkrétně funkcí ch_evalexpr(kanál, data):

ch_evalexpr(channel1, "Hello daemon")

Pokud démon vrátí nějakou zprávu, je tato zpráva vrácena jako výsledek funkce ch_evalexpr():

let response = ch_evalexpr(channel1, "Hello daemon")
echo response

Na konci skriptu je nutné kanál zavřít a uvolnit tak příslušný socket:

ch_close(channel1)

9. Úlohy (jobs)

Pro spuštění pluginů v samostatných procesech se používá nová funkce nazvaná job_start(). Tato funkce má jeden nepovinný parametr, kterým je příkaz pro spuštění (například cesta ke skriptu atd.). Nepovinným parametrem options je možné funkci job_start() předat způsob spuštění úlohy, například zda se mají pro komunikaci použít kanály či roury (pipy), jak dlouho se má čekat na dokončení volání (timeout) atd. Jakmile je úloha spuštěna, lze její stav zjistit zavoláním funkce job_status() vracející řetězec „run“, „fail“ (nepodařil se start) nebo „dead“ (start se podařil, nicméně úloha již skončila). Explicitní ukončení úlohy zajišťuje funkce pojmenovaná jednoduše job_stop(), které lze navíc v Linuxu předat signál pro ukončení: „term“, „hup“, „quit“ apod. A to nejdůležitější na konec: pokud se úloha spustila, je možné získat komunikační kanál funkcí job_channel() a následně použít techniku popsanou v předchozích třech kapitolách.

10. Využití synergie mezi všemi zmíněnými technologiemi: skutečně fungující asynchronně běžící skripty

Nyní již alespoň do určité míry známe všechny nové technologie, které je možné použít při tvorbě pluginů (přídavných modulů), které mají běžet asynchronně, tj. nezávisle na operacích prováděných uživatelem. Typicky si takto fungující pluginy připraví nějaké okno (Quickfix view atd.), do kterého postupně vypíšou informace získané při svém běhu. Asynchronně běžící pluginy bývají spuštěny přes časovač (kterému se předává funcref nebo anonymní funkce), pro komunikaci používají kanál (kanály) a vlastní komunikace typicky probíhá s využitím JSONu (i když to například u dále popsaných pluginů neplatí – ty totiž vykonávají poměrně jednoduché činnosti). Pojďme se tedy seznámit s trojicí praktických pluginů pojmenovaných jednoduše asyncrun, agrep a amake.

Obrázek 15: Nápověda k dále popsanému pluginu agrep.

11. Plugin asyncrun

První plugin, s nímž se v dnešním článku seznámíme, se jmenuje asyncrun. Název pluginu je to skutečně příhodný, protože tento přídavný modul slouží ke spuštění prakticky libovolného externího příkazu, jehož výsledek se následně objeví v quickfix okně. Pokud externí příkaz vypíše nějaké chyby, mohou být automaticky rozpoznány na základě volby errorformat (viz vestavěná nápověda :help errorformat) a bude možné použít standardní příkazy pro skok na předchozí resp. na následující chybu, což jsme si již ukázali v článku Textový editor Vim jako IDE (2.část). Důležité přitom je, že spuštěný externí příkaz může běžet relativně dlouho, ovšem uživatele to nijak neomezuje – ten může prakticky pokračovat v práci s textovým editorem. Po ukončení skriptu může být uživatel informován pípnutím (pokud jste si ovšem tuto možnost nevypnuli).

Obrázek 16: Volba errorformat je samozřejmě opět popsána v nápovědě.

Funkci tohoto pluginu pěkně ukazuje animovaný GIF, který naleznete na adrese https://raw.githubusercon­tent.com/skywind3000/asyn­crun.vim/master/doc/screen­shot.gif.

12. Instalace pluginu asyncrun

Před popisem instalace pluginu asyncrun se alespoň krátce zmiňme o další novince, kterou přinesl Vim 8. Jedná se o standardního správce balíčků.

Při instalaci většího množství pluginů prakticky vždy stojíme před závažným problémem – jak tyto pluginy instalovat či odinstalovat bez rozbití konfigurace ostatních pluginů? Jádro problému spočívá v tom, že v předchozích verzích Vimu (konkrétně od Vimu 5.0 do Vimu 7.x) byly cesty nastavené takovým způsobem, že se pluginy musely buď rozbalovat do společných adresářů (všechny nápovědy v jednom adresáři, všechny syntaktické soubory v dalším adresáři, skripty v adresáři třetím atd.) popř. bylo nutné použít některý správce pluginů – typicky Vundle či Pathogen. Ovšem situace, kdy se pro správu pluginů musel používat jiný plugin, byla … suboptimální :-). Z tohoto důvodu je ve Vimu 8 možné použít interního správce a postupně tak odstranit nutnost použití Vundle či Pathogenu. To ale nějaký čas potrvá, protože se musí stávající balíčky s pluginy upravit. Nicméně již dnes můžeme několik nových balíčků kompatibilních s Vimem 8 nalézt, popř. lze zkombinovat standardního správce s Vundlem či Pathogenem.

Obrázek 17: Nápověda ke standardnímu správci balíčků, který je součástí Vimu 8.

Při používání standardního správce balíčků z Vimu 8 je důležité, aby se pluginy po rozbalení umístily do správných adresářů, v nichž je bude Vim po svém spuštění a inicializaci hledat. Nejjednodušší je instalace pluginů pouze pro aktivního uživatele, protože v tomto případě je celá adresářová struktura umístěna v adresáři ~/.vim. V tomto adresáři musí být vytvořen podadresář nazvaný pack a v něm další podadresář s libovolným jménem (já jsem v příkladu použil podadresář nazvaný balicky, ale můžete použít i jiné označení):

[~/.vim]$ tree
 
.
└── pack
    └── balicky

Další dělení je jednoduché – v dalším podadresáři nazvaném opt jsou umístěny ty pluginy, které se mají načítat až na základě žádosti uživatele (optional), zatímco v podadresáři start budou umístěny ty pluginy, které se načtou a inicializují automaticky:

├── pack
│   └── balicky
│       ├── start
│       └── opt

Nyní se tedy konečně dostáváme k tomu, jak nainstalovat balíček asyncrun. Ve Vimu 8 k tomu potřebujeme pouze použít Git a naklonovat si příslušný repositář do adresáře ~/.vim/pack/balicky/start:

cd ~/.vim/pack/balicky/start
git clone https://github.com/skywind3000/asyncrun.vim

Výsledek by měl vypadat přibližně následovně:

.
├── autoload
├── doc
├── ftplugin
├── pack
│   └── balicky
│       └── start
│           └── asyncrun.vim
│               ├── doc
│               │   ├── asyncrun.txt
│               │   ├── cooperate_with_fugitive.gif
│               │   ├── errormarker.jpg
│               │   ├── screenshot.gif
│               │   └── simple.png
│               ├── LICENSE
│               ├── plugin
│               │   └── asyncrun.vim
│               └── README.md
├── plugin
├── spell
│   ├── cs.utf-8.add
│   ├── cs.utf-8.add.spl
│   ├── cs.utf-8.spl
│   ├── en.utf-8.add
│   └── en.utf-8.add.spl
└── syntax

Po naklonování a restartu Vimu bude balíček připraven k použití.

13. Použití pluginu asyncrun

Praktické použití tohoto pluginu je ve skutečnosti velmi jednoduché. Postačuje totiž kromě standardního příkazu :! libovolný_externí_příkaz argumenty použít volání :Asyncrun libovolný_externí_příkaz argumenty. Zadaný externí příkaz se spustí asynchronně a jeho výstup se postupně přidává do okna quickfix. Aby byl výstup skutečně viditelný, nastavte si globální proměnnou g:asyncrun_open na nenulovou hodnotu – viz též následující screenshot:

Obrázek 18: Nastavení hodnoty globální proměnné g:asyncrun_open.

Chování tohoto pluginu je možné ovlivnit několika globálními proměnnými:

Proměnná Význam
g:asyncrun_open výška quickfix okna (zadaná v počtu řádků), do kterého se vypíše výstup skriptu
g:asyncrun_bell povolení či zákaz pípnutí po dokončení skriptu
g:asyncrun_mode režim spuštění skriptu (synchronní, asynchronní, přes příkaz :shell)
g:asyncrun_exit lze specifikovat skript/funkci spuštěnou po dokončení asynchronního skriptu
g:asyncrun_encs kódování výstupu asynchronně spuštěného skriptu

Obrázek 19: Asynchronní spuštění skriptu ./build.sh.

14. Plugin agrep

Druhý plugin, s nímž se dnes ve stručnosti seznámíme, se jmenuje agrep. Opět se jedná o přiléhavý název, protože tento modul do značné míry nahrazuje příkazy :vimgrep a :grep. Navíc tento plugin zobrazuje výsledek vyhledání odlišným způsobem, který může být pro mnoho uživatelů čitelnější. Ostatně se sami podívejte na následující animovaný GIF, v němž jsou některé možnosti tohoto přídavného modulu ukázány. Na animaci je patrné, že vyhledávání skutečně probíhá asynchronně.

Obrázek 20: Nápověda k pluginu agrep.

Instalace pluginu agrep probíhá naprosto stejným způsobem, jako tomu bylo u předchozího pluginu Asyncrun:

cd ~/.vim/pack/balicky/start
git clone https://github.com/ramele/agrep

Výsledná struktura adresáře .vim může vypadat následovně:

.
├── autoload
├── doc
├── ftplugin
└── pack
    └── balicky
        └── start
            ├── agrep
            │   ├── doc
            │   │   └── agrep.txt
            │   ├── perl
            │   │   └── agrep.pl
            │   ├── plugin
            │   │   └── agrep.vim
            │   ├── README.md
            │   └── syntax
            │       ├── agrep.vim
            │       └── qf.vim
            └── asyncrun.vim
                ├── doc
                │   ├── asyncrun.txt
                │   ├── cooperate_with_fugitive.gif
                │   ├── errormarker.jpg
                │   ├── screenshot.gif
                │   └── simple.png
                ├── LICENSE
                ├── plugin
                │   └── asyncrun.vim
                └── README.md

15. Použití pluginu agrep

Tento plugin nabízí především příkaz Agrep, který akceptuje stejné argumenty, jako standardní grep:

:Agrep přepínače vzorek soubory/cesta

Například:

:Agrep -r requirements.txt .

nebo:

:Agrep -r 'foo.*bar' src/

Na následujícím screenshotu je ukázáno spuštění příkazu :Agrep nad celým domovským adresářem, což je samozřejmě potenciálně poměrně náročná operace. Nicméně nás doba trvání opět nemusí trápit, protože editor může být použitý i předtím, než celá operace doběhne:

Obrázek 21: Spuštění příkazu :Agrep nad celým domovským adresářem.

Vyhledávací proces postupně naplňuje okno s výsledky.

Obrázek 22: Výsledky vyhledání všech souborů „requirements.txt“.

Po vyhledání vzorků je možné použít další příkazy:

Příkaz Význam
:Aopen otevře okno s výsledky vyhledání (může se otevřít automaticky)
:Aclose opak předchozího; zavře okno s výsledky vyhledání
:Anext otevření souboru s dalším nalezeným textem
:Aprev otevření souboru s předchozím textem
:Astop dokáže zastavit vyhledávání (asynchronní proces)

Nejjednodušší je ovšem přepnutí do okna s výsledky vyhledávání. Po stisku klávesy Enter se zobrazí ten soubor, na němž se nachází textový kurzor. Okno s výsledky vyhledávání však zůstane stále zobrazeno, takže se k výsledkům můžete kdykoli vrátit.

Obrázek 23: Okno s výsledky vyhledání všech řetězců „test“ v souborech umístěných v domovském adresáři.

16. Plugin amake

Poslední plugin, s nímž se dnes seznámíme, se jmenuje vim-amake, ovšem běžně je jeho jméno zkracováno na amake. Tento modul částečně supluje funkci modulu předchozího, a to z toho důvodu, že uživatelům nabízí dva nové příkazy: :Amake a :Agrep (opět). Tyto příkazy akceptují stejné přepínače, jako standardní příkazy :make a :grep.

Obrázek 24: Minimalisticky pojatá nápověda k pluginu amake.

Instalace tohoto pluginu je snadná a probíhá stejným způsobem, jako tomu bylo u předchozích dvou modulů:

cd ~/.vim/pack/balicky/start
git clone https://github.com/edkolev/vim-amake

Nyní by měl celý adresář s pluginy vypadat takto:

.
├── autoload
├── doc
├── ftplugin
├── pack
│   └── balicky
│       └── start
│           ├── agrep
│           │   ├── doc
│           │   │   └── agrep.txt
│           │   ├── perl
│           │   │   └── agrep.pl
│           │   ├── plugin
│           │   │   └── agrep.vim
│           │   ├── README.md
│           │   └── syntax
│           │       ├── agrep.vim
│           │       └── qf.vim
│           ├── asyncrun.vim
│           │   ├── doc
│           │   │   ├── asyncrun.txt
│           │   │   ├── cooperate_with_fugitive.gif
│           │   │   ├── errormarker.jpg
│           │   │   ├── screenshot.gif
│           │   │   └── simple.png
│           │   ├── LICENSE
│           │   ├── plugin
│           │   │   └── asyncrun.vim
│           │   └── README.md
│           └── vim-amake
│               ├── autoload
│               │   └── amake.vim
│               ├── plugin
│               │   └── amake.vim
│               └── README.md
├── plugin
├── spell
│   ├── cs.utf-8.add
│   ├── cs.utf-8.add.spl
│   ├── cs.utf-8.spl
│   ├── en.utf-8.add
│   └── en.utf-8.add.spl
└── syntax

17. Použití pluginu amake

Tento plugin skutečně nabízí pouze dva nové příkazy, a to :Amake a :Agrep. Oba příkazy jsou spuštěny na pozadí, přičemž příkaz :Amake po svém dokončení pouze vypíše „Success: make“ popř.  zobrazí standardní chybové okno s informaci o tom, proč se překlad popř. slinkování nepovedlo (podobně pracuje i příkaz :make). Druhý podporovaný příkaz :Agrep používá quickfix okno, podobně jako oba dva pluginy popsané v předchozích kapitolách.

Obrázek 25: Spuštění příkazu :Amake.

Obrázek 26: Dokončení překladu a slinkování projektu.

bitcoin_skoleni

Obrázek 27: Příkaz :Amake samozřejmě akceptuje i parametry, které jsou předány utilitě make.

18. Okno terminálu (novinka ve Vimu 8.1)

Na tomto místě je vhodné upozornit na to, že do Vimu verze 8.1 byla přidána další velmi užitečná pomůcka, jejíž interní funkcionalita je zajištěna právě schopností nového Vimu spustit asynchronní kód. Jedná se o schopnost Vimu 8.1 přímo na ploše svého okna spustit terminál s prakticky libovolným příkazem, ať již se jedná o neinteraktivní skript (například onen make nebo grep), tak o interaktivní aplikaci. Možnosti terminálu spuštěného uvnitř Vimu asi nejlépe vyniknou na tomto screenshotu, v němž je okno Vimu rozděleno na tři oblasti: terminál s běžícím debuggerem gdb, oblast se spuštěním debuggeru atd. a v pravé části pak editovaný a laděný zdrojový kód. Povšimněte si, že v pravé oblasti je dokonce zobrazen i breakpoint (červená značka) a řádek, který je právě laděn při krokování v debuggeru. Podobně si samozřejmě můžete spustit i další nástroje, například interaktivní merge v Gitu atd. atd.

19. Odkazy na Internetu

  1. Vim – the editor
    http://www.vim.org/
  2. Vim 8.0 is released
    https://laravel-news.com/2016/09/vim-8–0-is-released/
  3. Vim: So long Pathogen, hello native package loading
    https://shapeshed.com/vim-packages/
  4. Asynchronous grep plugin for Vim
    https://github.com/ramele/agrep
  5. Run Async Shell Commands in Vim 8.0 / NeoVim and Output to Quickfix Window
    https://github.com/skywin­d3000/asyncrun.vim
  6. :smile command was not backported! #5116
    https://github.com/neovim/ne­ovim/issues/5116
  7. Run :make and :grep in background jobs (experimental)
    https://github.com/edkolev/vim-amake
  8. Favorite vim plugins
    http://sherifsoliman.com/2016/05/30/fa­vorite-vim-plugins/
  9. Makejob
    http://www.vim.org/scripts/scrip­t.php?script_id=5479
  10. Články o Vimu na Root.cz:
    http://www.root.cz/n/vim/clanky/
  11. Vim sedm – první část
    http://www.root.cz/clanky/vim-sedm-prvni-cast/
  12. vim (man page)
    http://www.linux-tutorial.info/modules.php?na­me=ManPage&sec=1&manpage=vim
  13. History of the Text Editor
    http://vanstee.me/history-of-the-text-editor.html
  14. Interview with Bill Joy
    http://web.cecs.pdx.edu/~kir­kenda/joy84.html
  15. vi Editor Commands
    http://www.cs.rit.edu/~cslab/vi­.html#A1.4
  16. vi Manual
    http://www.cs.fsu.edu/gene­ral/vimanual.html
  17. Mastering the Vi Editor
    http://www.susnet.uk/mastering-the-vi-editor
  18. Vim as a Python IDE, or Python IDE as Vim
    http://blog.jetbrains.com/pychar­m/2013/06/vim-as-a-python-ide-or-python-ide-as-vim/
  19. Vi Improved
    https://wiki.python.org/moin/Vim
  20. Popis skriptu Vim Pathogen
    http://www.vim.org/scripts/scrip­t.php?script_id=2332
  21. Posledníverze skriptu Vim Pathogen
    https://tpo.pe/pathogen.vim
  22. Nejlepší pluginy pro Vim
    http://vimawesome.com/
  23. Nejlepší pluginy pro Vim
    http://www.vim.org/scripts/scrip­t_search_results.php?order_by=ra­ting
  24. Building Vim
    http://vim.wikia.com/wiki/Bu­ilding_Vim
  25. Vim plugins for developers
    http://www.linuxtoday.com/upload/vim-plugins-for-developers-140619094010.html
  26. Writing Vim Plugins
    http://stevelosh.com/blog/2011/09/wri­ting-vim-plugins/
  27. how to understand this vim script?
    http://stackoverflow.com/qu­estions/12625091/how-to-understand-this-vim-script
  28. Novinky ve VIM 7: Skriptovací jazyk
    http://www.root.cz/vim-sedm-druha-cast/
  29. DirDiff.vim : A plugin to diff and merge two directories recursively.
    http://www.vim.org/scripts/scrip­t.php?script_id=102
  30. vim-dirdiff na GitHubu
    https://github.com/will133/vim-dirdiff
  31. fakeclip : pseudo clipboard register for non-GUI version of Vim
    http://www.vim.org/scripts/scrip­t.php?script_id=2098
  32. vim-fakeclip na GitHubu
    https://github.com/kana/vim-fakeclip
  33. vim-fakeclip: Dokumentace
    http://kana.github.io/con­fig/vim/fakeclip.html
  34. Vim Multiple Cursors na GitHubu
    https://github.com/terryma/vim-multiple-cursors
  35. SLIME (Wikipedia)
    http://en.wikipedia.org/wiki/SLIME
  36. vim-slime na GitHubu
    https://github.com/jpalardy/vim-slime
  37. The NERD tree: A tree explorer plugin for navigating the filesystem
    http://www.vim.org/scripts/scrip­t.php?script_id=1658
  38. JavaBrowser : Shows java file class, package in a tree as in IDEs. Java source browser.
    http://www.vim.org/scripts/scrip­t.php?script_id=588
  39. snippetsEmu : An attempt to emulate TextMate's snippet expansion
    http://www.vim.org/scripts/scrip­t.php?script_id=1318
  40. c.vim : C/C++ IDE key mappings
    http://lug.fh-swf.de/vim/vim-c/c-hotkeys.pdf
  41. Základní základy editoru Vim
    http://www.root.cz/clanky/zakladni-zaklady-editoru-vim/
  42. Jak si přizpůsobit Vim
    http://www.root.cz/serialy/jak-si-prizpusobit-vim/
  43. Taglist (plugin)
    http://www.vim.org/scripts/scrip­t.php?script_id=273
  44. Tutorial: Make Vim as Your C/C++ IDE Using c.vim Plugin
    http://www.thegeekstuff.com/2009/01/tu­torial-make-vim-as-your-cc-ide-using-cvim-plugin/
  45. c.vim : C/C++ IDE
    http://vim.sourceforge.net/scrip­ts/script.php?script_id=213
  46. The History of Vim
    https://jovicailic.org/2014/06/the-history-of-vim/
  47. Display date-and-time on status line
    http://vim.wikia.com/wiki/Display_date-and-time_on_status_line
  48. Vim 8.1 is available!
    https://www.vim.org/vim-8.1-released.php

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.