Použití Language Server Protocolu v textovém editoru Vim

25. 9. 2018
Doba čtení: 27 minut

Sdílet

Dnes se seznámíme s Language Server Protocolem (LSP) a s využitím této relativně nové technologie v textovém editoru Vim. Kromě Vimu se LSP používá například i ve Visual Studiu Code, Eclipse, Eclipse Che a ATOMu.

Obsah

1. Použití Language Server Protocolu v textovém editoru Vim

2. Základní informace o projektu LSP

3. Některé starší projekty založené na podobném principu

4. LSP v Eclipse, Visual Studiu Code, Eclipse Che a Atom IDE

5. Python Language Server

6. Kooperace mezi Python Language Serverem a Vimem

7. Využití nových technologií Vimu 8 v pluginu vim-lsp

8. Konfigurace pluginu vim-lsp

9. Dostupné příkazy poskytované pluginem vim-lsp

10. Popis nejdůležitějších příkazů

11. Ukázka použití

12. Technologie Omni Completion a další možnosti doplňování textu ve Vimu

13. Nastavení funkce Omni Completion pro plugin vim-lsp

14. Konfigurace klávesové zkratky pro vyvolání omni completion

15. Nastavení automatického doplňování volbou completeopt

16. Přijdou další protokoly – tentokrát pro ladění aplikací?

17. VSCode Debug Protocol

18. Ukázka konfiguračního souboru .vimrc připraveného pro Python

19. Popis jednotlivých částí konfiguračního souboru

20. Odkazy na Internetu

1. Použití Language Server Protocolu v textovém editoru Vim

Language Server Protocol je otevřený standard navržený takovým způsobem, aby umožňoval komunikaci mezi textovými editory popř. mezi integrovanými vývojovými prostředími (IDE) na jedné straně a různými typy programátorských nástrojů na straně druhé. Mezi nástroje, které je díky existenci LSP možné z editoru/IDE použít, mohou patřit zejména různé lintery, statické analyzátory kódu, programy pro kontrolu stylu zápisu programů, nástroje umožňující refaktoring zdrojového kódu, teoreticky i profilery atd. Nesmíme samozřejmě zapomenout na dnes již všemi programátory očekávané nástroje pro automatické doplňování jmen funkcí, metod, objektů atd., „inteligentní“ vyhledávání ve zdrojovém kódu, doskoky na definici funkce, objektu nebo proměnné apod. Všechny tyto nástroje mohou komunikovat s editorem/IDE přímo (pokud obsahují podporu pro LSP), nebo je možné využít nějaký obecnější nástroj, který je většinou nazývaný Language Server a který podporuje větší množství funkcí (typicky vyhledání definic, refaktoring a automatické doplňování; samozřejmě v závislosti na konkrétní implementaci).

Obrázek 1: Textový editor Emacs ve funkci integrovaného vývojového prostředí.

Poznámka: samotný protokol je nezávislý na tom, jestli Language Server běží na stejném počítači jako samotný editor/IDE, či zda běží na nějakém vzdáleném serveru (jako služba). V prvním případě samozřejmě budou odezvy rychlejší a doba odezvy by v ideálním případě neměla být uživatelem vůbec zaznamenána (tj. prostředí musí reagovat do max. jedné sekundy). Ve druhém případě se může jednat o sofistikovanější nástroje, které například používají vlastní databázi, AI modul atd.

2. Základní informace o projektu LSP

Samotný protokol je založen na formátu JSON, přesněji řečeno na protokolu JSON-RPC. Při použití LSP je textový editor či IDE považován za klienta a language server pochopitelně vystupuje v roli serveru, který klientovi poskytuje tzv. „language service“. Mezi klientem a serverem probíhá komunikace s využitím tří typů zpráv:

  • Požadavek (request) je posílán klientem na server. Server musí na tento požadavek odpovědět. Používá se protokol JSON-RPC.
  • Odpověď (response) serveru na požadavek klienta. Klient vždy dokáže spárovat svůj požadavek s odpovědí serveru (to je základ JSON-RPC).
  • Oznamovací zprávy (notification) může posílat jak klient, tak i server, přičemž se na tyto zprávy nemusí posílat odpověď. Klient (editor) může například serveru ohlásit, že uživatel posunul kurzor na další funkci, ale prozatím od něj neočekává žádnou další službu.

Příklad komunikace (na levé straně je klient/editor, na straně pravé language server):

textDocument/didOpen ->
textDocument/didChange ->
<- textDocument/publishDiagnostics
<- textDocument/didClose

Konkrétní tvar dotazu může vypadat takto:

{'method': 'textDocument/hover', 'jsonrpc': '2.0', 'id': 4,
 'params': {'textDocument': {'uri': 'file:///home/ptisnovs/t.py'},
 'position': {'character': 0, 'line': 21}}}

Jeden typ notifikace vrácené serverem klientovi:

textDocument/publishDiagnostics {'uri': 'file:///home/ptisnovs/t.py',
'diagnostics': [{'source': 'pycodestyle',
'range': {'start': {'line': 3, 'character': 19},
          'end': {'line': 3, 'character': 21}},
          'message': 'W291 trailing whitespace', 'code': 'W291', 'severity': 2}]}

3. Některé starší projekty založené na podobném principu

Myšlenka, na které je language server protocol postaven, samozřejmě není nijak nová ani převratná, protože se podobný princip využíval již dříve, například v textových editorech Vim a Emacs, které takto mohly komunikovat s nástroji běžícími na pozadí (daemons), viz též https://github.com/Microsoft/language-server-protocol/wiki/Protocol-History. Příkladem může být integrace nástroje Jedi do Vimu, o níž jsme se nedávno zmínili v článku Knihovna Jedi: doplňování kódu a statická analýza kódu v Pythonu.

Poznámka: to, že je LSP vůbec reálně využitelný, je do značné míry zajištěno díky vysokému výpočetnímu výkonu současných (desktopových) počítačů. Dříve byly podobné nástroje velmi úzce propojeny se samotným editorem právě z toho důvodu, aby byla odezva editoru/IDE dostatečně rychlá. Na druhou stranu se samozřejmě nejedná o univerzální řešení a dnes již například IDE specializované na jediný jazyk nebývají tolik oblíbena, jako v minulosti, což je kvůli rostoucí složitosti a provázanosti technologií v IT pochopitelné.

Obrázek 2: Dnes již historické IDE Turbo Pascalu určené pouze pro jediný jazyk. Prakticky všechny nástroje jsou nedílnou součástí tohoto IDE (s výjimkou externích nástrojů grep a Turbo Debugger).

4. LSP v Eclipse, Visual Studiu Code, Eclipse Che a Atom IDE

Protokol LSP se v současnosti již běžně používá. Podporu pro něj nalezneme především ve Visual Studiu Code, což je ovšem pochopitelné, protože myšlenka LSP vznikla právě v Microsoftu pro potřeby tohoto dnes populárního integrovaného vývojového prostředí. Nesmíme ovšem zapomenout ani na klasické Eclipse, v němž je možné LSP používat díky pluginu Eclipse LSP4E. Na LSP je taktéž postavena velká část funkcionality webového integrovaného vývojového prostředí Eclipse Che. Další aplikací, která je založena na použití LSP, je Atom IDE, což je ve skutečnosti známý textový editor Atom doplněný o několik pluginů, především pak o plugin Atom Language Server Protocol Client.

Obrázek 3: Editor Atom použitý jako jednoduché integrované vývojové prostředí.

5. Python Language Server

V dalších kapitolách si ukážeme, jakým způsobem je možné integrovat language server protocol do Vimu s využitím pluginu vim-lsp. Aby bylo možné použít reálné příklady, použijeme jednu konkrétní implementaci LSP určenou pro programovací jazyk Python. Tato implementace se jmenuje jednoduše Python Language Server, kráceně PYLS či jen pyls. Instalace Python Language Serveru je jednoduchá. Postačuje použít nástroj pip nebo pip3 a provést instalaci lokálně pro právě přihlášeného uživatele:

$ pip3 install --user python-language-server

Průběh instalace závisí na tom, zda již máte nainstalovány všechny závislé balíčky či nikoli. V mém případě jsem již některé balíčky měl nainstalované (minimálně od doby psaní článku o Jedi), takže instalace byla prakticky okamžitá:

Collecting python-language-server
  Downloading https://files.pythonhosted.org/packages/9f/1d/2817b5dc2dd77f897410a11c1c9e2a6d96b3273c53d4219dd9edab7882af/python-language-server-0.21.2.tar.gz (51kB)
Requirement already satisfied: future>=0.14.0 in ./.local/lib/python3.6/site-packages (from python-language-server)
Requirement already satisfied: jedi>=0.12 in ./.local/lib/python3.6/site-packages (from python-language-server)
Requirement already satisfied: python-jsonrpc-server in ./.local/lib/python3.6/site-packages (from python-language-server)
Requirement already satisfied: pluggy in /usr/lib/python3.6/site-packages (from python-language-server)
Requirement already satisfied: parso>=0.3.0 in ./.local/lib/python3.6/site-packages (from jedi>=0.12->python-language-server)
Installing collected packages: python-language-server
  Running setup.py install for python-language-server: started
    Running setup.py install for python-language-server: finished with status 'done'
Successfully installed python-language-server-0.21.2

Pro velmi rychlou kontrolu, zda instalace proběhla v pořádku, si můžete Python Language Server spustit a zjistit, zda příkaz vůbec existuje popř. zda při spuštění nedojde k nějaké chybě:

$ pyls

Prozatím můžeme server ukončit pomocí Ctrl+C, protože dále popisovaný plugin vim-lsp si server dokáže spustit sám automaticky.

6. Kooperace mezi Python Language Serverem a Vimem

V tomto okamžiku máme nainstalován jak editor Vim (doufejme), tak i Python Language Server. Zbývá nám tedy jen „maličkost“ – propojit tyto dva nástroje a začít používat všechny funkce Python Language Serveru přímo z Vimu. K tomuto účelu je možné použít plugin vim-lsp, který naleznete na GitHubu, konkrétně na adrese https://github.com/prabirshrestha/vim-lsp. Instalaci je možné provést různými způsoby (například využít různé správce balíčků), ovšem lze zůstat u konzervativní metody a rozbalit všechny soubory přímo do adresáře ~./vim. Stejným způsobem (rozbalením popř. prostým naklonováním) je nutné nainstalovat i závislý plugin async.vim.

V podadresáři ~/.vim by měla být vytvořena následující adresářová struktura (samozřejmě zde můžeme mít uloženy další pluginy, ostatně i já zde mám nainstalovány například slovníky):

.
├── autoload
│   ├── async
│   │   └── job.vim
│   ├── lsp
│   │   ├── capabilities.vim
│   │   ├── client.vim
│   │   ├── omni.vim
│   │   ├── ui
│   │   │   ├── vim
│   │   │   │   ├── diagnostics
│   │   │   │   │   └── echo.vim
│   │   │   │   ├── diagnostics.vim
│   │   │   │   ├── hover.vim
│   │   │   │   ├── output.vim
│   │   │   │   ├── signs.vim
│   │   │   │   └── utils.vim
│   │   │   └── vim.vim
│   │   ├── utils
│   │   │   └── step.vim
│   │   └── utils.vim
│   └── lsp.vim
├── doc
│   └── vim-lsp.txt
├── ftplugin
│   └── lsp-hover.vim
├── plugin
│   └── lsp.vim
├── pythonx
├── 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 spuštění Vimu a napsání:

:Lsp<Tab>

by se měly vypsat všechny nové příkazy začínající prefixem „Lsp“.

Následuje dokončení instalace, zejména indexace nápovědy. Spusťte textový editor Vim a zadejte příkaz:

:helptags ~/.vim/doc

7. Využití nových technologií Vimu 8 v pluginu vim-lsp

Na tomto místě je vhodné upozornit na to, že plugin vim-lsp vyžaduje pro svou korektní činnost Vim 8. Ve starších verzích nebude modul fungovat. Je tomu tak z toho prostého důvodu, že (jak již víme) se pro komunikaci mezi LSP klientem a LSP serverem používá JSON-RPC a funkce pro serializaci a deserializaci dat do JSONu nalezneme až ve skriptovacím engine Vimu 8. Pravděpodobně by bylo možné si tyto funkce dopsat i pro Vim 6 nebo Vim 7, ovšem výhodnější je update editoru.

Jen pro krátké připomenutí informací z článku o novinkách ve Vimu 8: do skriptovacího engine Vimu byly přidány čtyři nové funkce, které slouží pro převod datových struktur VimScriptu do JSON formátu 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, JavaScript/TypeScript apod.).

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 VimScriptu (decode). Dvě varianty jsou implementovány z toho prostého důvodu, že někdy 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, ne vždy jsou totiž klíče současně i platnými identifikátory):

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 VimScriptu
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.

V pluginu vim-lsp nalezneme funkce json_encode a json_decode.

8. Konfigurace pluginu vim-lsp

Plugin vim-lsp by se měl inicializovat automaticky, protože jeho část je umístěna v podadresáři ~.vim/autoload. Nápovědu jsme též již inicializovali, takže by mělo být možné zadat příkaz:

:help vim-lsp

Tento příkaz by měl zobrazit stránku s popisem všech příkazů poskytovaných pluginem:

vim-lsp
 
================================================================================
CONTENTS                                                  vim-lsp-contents
 
    Introduction                vim-lsp-introduction
    Install                     vim-lsp-install
    Language Servers            vim-lsp-language-servers
      Configure                   vim-lsp-configure-source
      Wiki                        vim-lsp-configure-wiki
    Options                     vim-lsp-options
      g:lsp_auto_enable           g:lsp_auto_enable
      g:lsp_preview_keep_focus    g:lsp_preview_keep_focus
    Functions                   vim-lsp-functions
      enable                      vim-lsp-enable
      disable                     vim-lsp-disable
      register_server             vim-lsp-register_server
      stop_server                 vim-lsp-stop_server
    Commands                    vim-lsp-commands

Dále je nutné funkcí (ne příkazem!) lsp#register_server zaregistrovat jednotlivé jazykové servery, které chcete použít. Této funkci se předává jméno serveru, příkaz pro jeho spuštění a taktéž seznam jazyků, pro které je tento server určen. Následující skript zajistí načtení Python Language Serveru. Vložte tento skript do svého .vimrc, kdekoli za příkaz :set nocompatible:

if (executable('pyls'))
    au User lsp_setup call lsp#register_server({
        \ 'name': 'pyls',
        \ 'cmd': {server_info->['pyls']},
        \ 'whitelist': ['python']
        \ })
endif

Pokud budete chtít sledovat logy produkované jazykovým serverem, použijte mírně modifikovaný příkaz:

if (executable('pyls'))
    au User lsp_setup call lsp#register_server({
        \ 'name': 'pyls',
        \ 'cmd': {server_info->['pyls', '-vv', '--log-file', '/tmp/pyls.txt']},
        \ 'whitelist': ['python']
        \ })
endif

Po znovuspuštění Vimu se můžete přesvědčit o to, jestli byl jazykový server spuštěn či nikoli:

:LspStatus

s odpovědí:

pyls: running

nebo:

pyls: not running

Pokud server běží, lze o něm získat další informace:

:echo lsp#get_server_info('pyls')
 
{'cmd': function('<lambda>1'), 'name': 'pyls', 'whitelist': ['python']}

Samozřejmě se můžeme přesvědčit i na dalším terminálu, zda byl jazykový server spuštěn:

$ ps -ax |grep pyls
20787 ?        Ssl    0:01 /usr/bin/python3 /home/ptisnovs/.local/bin/pyls
20800 pts/3    S+     0:00 grep pyls

9. Dostupné příkazy poskytované pluginem vim-lsp

Všechny nové příkazy, které jsou poskytované pluginem vim-lsp, jsou i se stručným popisem vypsány v následující tabulce:

# Příkaz Stručný popis příkazu
1 LspDocumentDiagnostics spuštění diagnostiky dokumentu, viz další kapitolu
2 LspDefinition doskok na definici objektu, jehož jméno se nachází pod kurzorem
3 LspDocumentFormat zformátování dokumentu (není vždy implementováno)
4 LspDocumentRangeFormat zformátování části dokumentu
5 LspDocumentSymbol vypíše všechny symboly nalezené v aktuálně editovaném modulu
6 LspHover otevře nové okno s podrobnějšími informacemi o objektu, který se nalézá pod kurzorem
7 LspNextError přechod na následující detekovanou chybu
8 LspPreviousError přechod na předcházející detekovanou chybu
9 LspImplementation nalezne všechny implementace rozhraní (záleží na jazyku)
10 LspReferences nalezne všechny reference (volání/definice funkcí, metod atd.)
11 LspRename symbol (a jeho další výskyty) pod kurzorem se přejmenují
12 LspTypeDefinition doskok na definici typu (záleží na jazyku)
13 LspWorkspaceSymbol vyhledání symbolu
14 LspStatus stav jazykového serveru (běží/neběží)

10. Popis nejdůležitějších příkazů

Jedním z nejužitečnějších příkazů je :LspHover. Tento příkaz otevře nové okno s podrobnějšími informacemi o objektu, který se nalézá pod kurzorem. Toto informační okno lze zavřít klávesovou zkratkou Ctrl+W Ctrl+Z.

Dalším důležitým příkazem je :LspDocumentDiagnostic. Ten slouží pro zobrazení výsledků produkovaných linterem a taktéž nástrojem pro kontrolu dokumentačních řetězců. Výsledky se zobrazí v takzvaném Quickfix oknu, v němž každý řádek odpovídá jednomu problému. Výběrem řádku a stiskem klávesy Enter se otevře ta část dokumentu, která byla označena jako potenciálně problematická.

Příkaz :LspDocumentSymbol vypíše všechny symboly (funkce, metody, třídy, proměnné) nalezené v aktuálně editovaném modulu. Výsledky se opět zobrazí v takzvaném Quickfix oknu, v němž každý řádek odpovídá jednomu nalezenému symbolu.

A konečně příkaz :LspRename slouží pro přejmenování symbolu pod kurzorem (a jeho dalších výskytů). Po zadání tohoto příkazu se Vim zeptá na nové jméno, které následně použije. V případě Pythonu vyžaduje opravenou verzi.

11. Ukázka použití

Podívejme se nyní na způsob použití některých výše zmíněných příkazů. Otevřeme si ve Vimu jednoduchý skript naprogramovaný v Pythonu:

def hello_world():
    print("Hello world")
 
 
def hex():
    """Foobar"""
    print("HEX")
 
 
hex(40)
hello_world

Následuje několik screenshotů ukazujících základní možnosti pluginu vim-lsp společně s Python Language Serverem:

Obrázek 4: Nápověda k pluginu.

Obrázek 5: Ruční zavolání příkazu LspHover.

Obrázek 6: Výsledkem je pomocné okno s informacemi o objektu „hello_world“.

Obrázek 7: Napíšeme prefix „he“ a zadáme příkaz LspDocumentSymbol.

Obrázek 8: Výsledkem je výpis symbolů začínajících na tento prefix. Seznam je otevřen v Quickfix okně.

Obrázek 9: Klasické chování omnicompletion. Povšimněte si dynamické nápovědy v horním okně.

Obrázek 10: Klasické chování omnicompletion. Povšimněte si dynamické nápovědy v horním okně.

Obrázek 11: Vylepšení omnicompletion o specifikaci typu objektu.

Obrázek 12: Nápověda se samozřejmě zobrazí i k funkcím se základní knihovny (zde funkce print).

12. Technologie Omni Completion a další možnosti doplňování textu ve Vimu

Velmi užitečnou vlastností textového editoru Vim, kterou se dnes budeme v souvislosti s LSP zabývat, je technologie nazvaná „omni completion“ (někdy se setkáme i s jednoslovným zápisem „omnicompletion“). Tato technologie, která se ve Vimu objevila až v jeho sedmé verzi, rozšiřuje možnosti automatického doplňování kódu (či obecně textu) o externí nástroje. Připomeňme si, že Vim nabízí ve vkládacím a přepisovacím režimu klávesovou zkratku Ctrl+P (previous) pro nalezení a doplnění slova nacházejícího se před kurzorem a taktéž zkratku Ctrl+N (next), která slouží ke stejnému účelu, ovšem hledá slovo pro doplnění v textu za kurzorem (pokud je k dispozici více možností, zobrazí se v kontextovém menu). V praxi tedy postačuje napsat jen začátek slova a stlačit Ctrl+P nebo Ctrl+N. Rozsah vyhledávání se specifikuje volbou complete popsanou zde a samozřejmě i ve vestavěné nápovědě.

Obrázek 13: Doplňování kódu nebo libovolného textu pomocí příkazů CTRL+P a CTRL+N zavolaných v režimu zápisu (insert mode).

Ovšem možnosti automatického doplňování kódu jsou ve skutečnosti daleko větší a textový editor Vim pro ně dokonce nabízí samostatný režim vyvolávaný z vkládacího či přepisovacího režimu klávesovou zkratkou Ctrl+X (právě z tohoto důvodu se tento režim nazývá ^X-mode nebo též CTRL-X mode). Po stlačení této klávesové zkratky se v příkazové řádce objeví řádkové menu s příkazy platnými pro režim doplňování:


Obrázek 14: Menu s dostupnými klávesovými zkratkami platnými v režimu CTRL-X.

Všechny dostupné příkazy jsou vypsány v tabulce níže:

# Příkaz Význam
1 Ctrl+X Ctrl+L nalezení a doplnění celého (shodného) řádku, užitečné především v případě editace některých typů konfiguračních souborů
2 Ctrl+X Ctrl+N doplnění slova, které se nalézá v aktuálně editovaném souboru
3 Ctrl+X Ctrl+K podobné Ctrl+N, ovšem slova se hledají v souborech specifikovaných pomocí konfiguračního parametru dictionary (jedná se o běžný textový soubor se seznamem slov)
4 Ctrl+X Ctrl+T podobné Ctrl+T, ovšem slova se hledají v souborech specifikovaných pomocí konfiguračního parametru thesaurus
5 Ctrl+X Ctrl+I podobné Ctrl+N, ovšem prohledávají se i všechny vkládané (included) soubory
6 Ctrl+X Ctrl+] vyhledávání v seznamu značek
7 Ctrl+X Ctrl+F doplnění názvu souboru a/nebo cesty, postačuje například zadat text ~/ za nímž následuje klávesová zkratka Ctrl+X Ctrl+F a zobrazí se výpis souborů v domácím adresáři
8 Ctrl+X Ctrl+D vyhledání definice makra a doplnění jména tohoto makra
9 Ctrl+X Ctrl+U zavolání funkce zadané v konfiguračním parametru completefunc, které se předá právě editovaný text
10 Ctrl+X Ctrl+O vyvolání funkce omni completion popsané v následující kapitole (dostupné od Vimu 7)

Obrázek 15: Doplnění názvu souboru v pracovním adresáři pomocí příkazu CTRL+X CTRL+F.

13. Nastavení funkce Omni Completion pro plugin vim-lsp

V předchozí kapitole jsme si řekli, že s využitím klávesové zkratky Ctrl+X Ctrl+O lze ve vkládacím a přepisovacím režimu zavolat technologii „omni completion“. Tuto technologii lze využít pro (pseudo)inteligentní doplňování textů založeném na analýze zdrojových kódů. Podobnou funkci můžeme najít v nejrůznějších integrovaných vývojových prostředích (Eclipse, Netbeans, Visual Studio, Visual Studio Code, nověji například i v Atomu), v nichž lze doplňovat například jména funkcí a metod, názvy prvků ve strukturách či uniích, atributů objektů, metod objektů či tříd, jmen balíčků atd. Plugin vim-lsp tuto funkci samozřejmě nabízí; její jméno je lsp#complete. Aby byla tato funkce skutečně použita po stisku Ctrl+X Ctrl+O je nutné tuto funkci přiřadit konfigurační volbě omnifunc.

Implicitně tato funkce není specifikována vůbec, o čemž se můžete velmi snadno přesvědčit při spuštění „prázdného“ Vimu:

:set omnifunc?
   omnifunc=

Naopak při editaci tohoto článku (v HTML) je funkce nastavena, a to konkrétně standardním filetype pluginem html.vim:

:set omnifunc?
   omnifunc=htmlcomplete#CompleteTags

V případě, že chceme použít možnosti nabízené pluginem vim-lsp, můžeme omnifunc nastavit globálně (pro celý Vim). To se provede jednoduše příkazem:

:set omnifunc=lsp#complete

Popř. lze stejnou funkci nastavit pouze lokálně pro právě aktivní buffer:

:setlocal omnifunc=lsp#complete

Samozřejmě nemusíme toto nastavení stále provádět ručně po otevření každého zdrojového souboru, ale můžeme do konfiguračního souboru .vimrc přidat příkaz, který se automaticky zavolá při otevření souboru s koncovkou *.py (Python):

augroup __python__
  au!
  au BufRead,BufNewFile *.py setlocal omnifunc=lsp#complete
augroup END
Poznámka: další možnosti nastavení budou popsány v osmnácté a devatenácté kapitole.

14. Konfigurace klávesové zkratky pro vyvolání omni completion

Výše uvedená konfigurace sice teoreticky postačuje po prakticky plnohodnotné využití možností pluginu vim-lsp, ovšem skalní uživatelé Vimu pravděpodobně očekávají nějaká vylepšení, která by Vim přiblížila k moderním IDE. Týká se to mj. i klávesové zkratky použité pro vyvolání funkce omni completion, protože Ctrl+X Ctrl+O je spíše „emacsovina“ :-) Řešení samozřejmě existuje několik. Pokud se ve zdrojových textech nikde nepoužívá tabulátor (záleží samozřejmě na zvolené „štábní kultuře“ a taktéž i na zvyklostech platící pro daný programovací jazyk), může pomoci následující mapování umožňující, aby se místo Ctrl+X Ctrl+O mohla stlačit pouze klávesa Tab:

:imap <Tab> <C-X><C-O>
Poznámka: v případě Pythonu se sice znak Tab (přesněji HT – horizontal tab) ve zdrojových kódech nemá používat, ovšem klávesa Tab může sloužit pro vkládání mezer odpovídajících jednomu odsazení. Záleží tudíž jen na vás, zda si tuto klávesu budete „rezervovat“ pro omni completion či nikoli. Alternativně je možné napsat funkci, která bude na začátku řádku klávesu Tab používat pro vkládání mezer a naopak po stisku Tab za písmenem pro zavolání omni completion. Podobné řešení bylo použito v pluginu IndentTab popř. pluginu Smart-Tabs. Další ideu naleznete například na stránce Smart mapping for tab completion

V případě, že používáte GVim nebo KVim, lze namísto klávesy Tab použít například i klávesovou zkratku Ctrl+Space:

:imap <C-Space> <C-X><C-O>

Na terminálech sice většinou není klávesová zkratka Ctrl+Space korektně rozpoznána, ale můžete se pokusit zjistit, zda terminál namísto Ctrl+Space nerozpozná Ctrl+@, tj. přesněji řečeno, zda obě klávesové zkratky nejsou rozpoznány jako shodný kód. Pokud tomu tak je, můžete mapování změnit na:

:imap <C-@> <C-X><C-O>

a používat Ctrl+Space i v terminálu.

Poznámka: technicky se klávesovou zkratkou Ctrl+@ zadává ASCII kód NUL, tj. první znak v ASCII tabulce, protože znak @ má ASCII kód 64 a stiskem modifikátoru Ctrl snižujeme ASCII kód právě o hodnotu 64. To je také důvod, proč stisk Ctrl+I tak odpovídá znaku HT (Horizontal Tab), Ctrl+H znaku BS (Backspace), Ctrl+[ řídicímu znaku ESC, Ctrl+M konci řádku (CR) atd. Vše osvětlí pohled na ASCII tabulku.

15. Nastavení automatického doplňování volbou completeopt

Chování automatického doplňování je řízeno volbou completeopt. Implicitní nastavení této volby získáme takto:

:set completeopt?
  completeopt=menu,preview

Hodnota menu povoluje zobrazení menu s nabídkami doplnění, ovšem pouze v případě, že existují dvě či více variant. Hodnota preview říká, že se k samotnému doplňovanému textu přidají například informace o souboru, kde byl nalezen atd. Existují ovšem i další volby, zejména menuone pro zobrazení menu i v případě, kdy byla nalezena jen jediná shoda a hodnota longest řídicí zobrazení položek textu, který odpovídá zadanému prefixu (nevybere se žádná možnost doplnění, uživatel však může výběr postupně zužovat klávesou Ctrl+L nebo rozšiřovat prostým zápisem dalších znaků, což je pro mnoho uživatelů přirozenější chování):

:set completeopt=longest,menuone

Dále je možné upravit chování klávesy Enter. Interně se totiž při automatickém doplňování může editor nacházet v jednom ze tří stavů a v každém stavu se Enter chová poněkud odlišně (někdy vkládá konec řádky, což je nepříjemné). Toto chování zakážeme následujícím příkazem, po jehož zápisu do .vimrc se Enter bude chovat vždy stejně (nikdy neodřádkuje):

:inoremap <expr> <CR> pumvisible() ? "\<C-y>" : "\<C-g>u\<CR>"
Poznámka: přesněji řečeno se Enter začne chovat jako klávesová zkratky Ctrl+Y (yes).

16. Přijdou další protokoly – tentokrát pro ladění aplikací?

Po přečtení předchozích kapitol možná čtenáře napadlo, že existence LSP je sice skvělá a užitečná věc pro samotné psaní programového kódu, pro hledání chyb, formátování kódu, refaktorizaci, statickou analýzu atd., ovšem jedna podstatná věc v protokolu chybí. Jedná se samozřejmě o možnosti ladění aplikací. Bylo by pravděpodobně velmi užitečné mít k dispozici jeden protokol, který by umožňoval spustit libovolný debugger (například GDB nebo LLDB, debugger Pythonu atd. atd.) a ovládat tento debugger jedním standardizovaným způsobem.

Obrázek 16: Příklad komunikace aplikace s GUI (zde Nemiver) s GNU Debuggerem.

Snahy o vytvoření takového protokolu samozřejmě nejsou nijak nové, protože podobný přístup nalezneme například v již zmíněném GNU Debuggeru, který lze spustit a ovládat z jiné aplikace (IDE) a navíc obsahuje i možnost ladění aplikací běžících na jiném počítači přes GDB Remote Serial Protocol. Novější přístup nabízí VSCode Debug protokol, s jehož základy se seznámíme v sedmnácté kapitole.

Obrázek 17: Klasické rozhraní GNU Debuggeru ovládaného z příkazového řádku.

Obrázek 18: GNU Debugger ovládaný z aplikace KDbg.

17. VSCode Debug Protocol

Jedním z navrhovaných univerzálních protokolů určených pro komunikaci mezi textovým editorem či IDE na jedné straně a debuggerem na straně druhé, je VSCode Debug Protocol. Jedná se vlastně o obdobu LSP, v níž se ovšem namísto Language Serveru komunikuje s takzvaným Debug Adapterem, který již může pracovat přímo se zvoleným debuggerem. Podporované debuggery (nebo podobně pracující nástroje) naleznete na této adrese. Zajímavé je, že mezi debuggery nalezneme i Z80 Debugger s typickou ikonou.

18. Ukázka konfiguračního souboru .vimrc připraveného pro Python

Podívejme se nyní na to, jak by mohl vypadat konfigurační soubor .vimrc připravený pro práci s Pythonem, přičemž jediným externím pluginem, který použijeme, bude právě modul vim-lsp. Snažil jsem se o minimalizaci počtu konfiguračních voleb v tomto souboru. Jednotlivé části konfiguračního souboru budou popsány v navazující kapitole:

" .vimrc by Tisnik
 
" zakladni volby
set nocompatible                " rezim nekompatibilni s vi, pouzivejte jako prvni volbu
set encoding=utf-8              " interni format znaku ve Vimu (neovlivnuje nacitani a ukladani)
set novisualbell                " pri chybe se nepipa, ale zablika obrazovka
set hlsearch                    " zvyrazneni vysledku hledani
set incsearch                   " zvyrazneni pri hledani
set showmatch                   " bude se zobrazovat prislusna druha zavorka
set showmode                    " bude se zobrazovat rezim cinnosti (-- INSERT --, -- REPLACE --, -- VISUAL -- ...)
set showcmd                     " bude se zobrazovat prave zadavany prikaz (3dd ...)
set ruler                       " bude se zobrazovat pravitko s pozici kurzoru
set shiftwidth=4                " pocet mezer pri posunu bloku pomoci << a >>
set expandtab                   " expanze znaků Tab na mezery
set tabstop=4                   " pocet mezer odpovidajicich znaku Tab
set bs=2                        " backspace maze vse
 
" adresare pro docasne soubory, zalohy atd.
set backupdir=~/temp,.          " adresar pro ulozeni zaloznich souboru
set directory=~/temp,.          " adresar pro swapovaci soubor
set viminfo='20,\"50,n~/temp/_viminfo
 
" nastaveni pro Python
augroup __python__
  au!
  au BufRead,BufNewFile *.py map <F1> :LspStatus<cr>
  au BufRead,BufNewFile *.py map <F2> :!python2 %<cr>
  au BufRead,BufNewFile *.py map <F3> :!python3 %<cr>
  au BufRead,BufNewFile *.py map <F4> :LspDocumentDiagnostics<cr>
  au BufRead,BufNewFile *.py map <F5> :LspHover<cr>
  au BufRead,BufNewFile *.py map <F6> :LspDocumentSymbol<cr>
  au BufRead,BufNewFile *.py map <F8> :!pep8 %<cr>
  au BufRead,BufNewFile *.py map <F9> :!pydocstyle ./%<cr>
  au BufRead,BufNewFile *.py map <F10> :!radon cc ./%<cr>
  au BufRead,BufNewFile *.py map <F11> :!pudb3 %<cr>
  au BufRead,BufNewFile *.py map <F12> <C-W><C-Z>
  au BufRead,BufNewFile *.py imap <F12> <C-O><C-W><C-Z>
  au BufRead,BufNewFile *.py imap <Tab> <C-X><C-O>
  au BufRead,BufNewFile *.py highlight OverLength ctermbg=yellow ctermfg=white guibg=#592929
  au BufRead,BufNewFile *.py match OverLength /\%99v.\+/
  au BufRead,BufNewFile *.py setlocal omnifunc=lsp#complete
augroup END
 
" spusteni Python Language Serveru
if (executable('pyls'))
    au User lsp_setup call lsp#register_server({
        \ 'name': 'pyls',
        \ 'cmd': {server_info->['pyls']},
        \ 'whitelist': ['python']
        \ })
endif

19. Popis jednotlivých částí konfiguračního souboru

První část souboru .vimrc obsahuje obecná nastavení, z nichž se Pythonu týká především nastavení chování znaku Tab. Podle PEP 8 se nemají znaky Tab ve zdrojových kódech používat, takže nastavíme expanzi Tabů na mezery a určíme odsazení bloků o čtyři znaky (pro posun bloků doleva a doprava budou sloužit příkazy << a >>, popř. jen < a > ve chvíli, kdy je blok vybrán vizuálně):

set shiftwidth=4                " pocet mezer pri posunu bloku pomoci << a >>
set expandtab                   " expanze znaků Tab na mezery
set tabstop=4                   " pocet mezer odpovidajicich znaku Tab

Dále nastavíme několik klávesových zkratek pro vyvolání externích příkazů. První dvě zkratky jsou mnemotechnické:

Režim Klávesa Funkce
normální F2 spuštění aktuálně editovaného souboru Pythonem 2
normální F3 spuštění aktuálně editovaného souboru Pythonem 3
normální F8 kontrola aktuálně editovaného souboru nástrojem pep8
normální F9 kontrola zápisu dokumentačních řetězců (existence, styl)
normální F10 změření cyklomatické složitosti nástrojem radon
normální F11 spuštění aktuálně editovaného souboru debuggerem pydb
  au BufRead,BufNewFile *.py map <F2> :!python2 %<cr>
  au BufRead,BufNewFile *.py map <F3> :!python3 %<cr>
  au BufRead,BufNewFile *.py map <F8> :!pep8 %<cr>
  au BufRead,BufNewFile *.py map <F9> :!pydocstyle ./%<cr>
  au BufRead,BufNewFile *.py map <F10> :!radon cc ./%<cr>
  au BufRead,BufNewFile *.py map <F11> :!pudb3 %<cr>

Třetí skupina klávesových zkratek volá funkce pluginu vim-lsp, o nichž jsme se zmínili výše:

Režim Klávesa Funkce
normální F1 LspStatus – stav LSP, připojení k serveru atd.
normální F4 LspDocumentDiagnostics – analýza a nalezení případných chyb ve zdrojovém kódu
normální F5 LspHover – informace o symbolu, na němž je kurzor
normální F6 LspDocumentSymbol – nalezení symbolů začínajících daným prefixem
  au BufRead,BufNewFile *.py map <F1> :LspStatus<cr>
  au BufRead,BufNewFile *.py map <F4> :LspDocumentDiagnostics<cr>
  au BufRead,BufNewFile *.py map <F5> :LspHover<cr>
  au BufRead,BufNewFile *.py map <F6> :LspDocumentSymbol<cr>

Poslední skupina kláves obsahuje mapování související s pomocnými okny i s výše popsanou funkcí omni completion:

bitcoin_skoleni

Režim Klávesa Funkce
normální F12 uzavření náhledového okna vytvořeného některými funkcemi LSP
vkládací F12 uzavření náhledového okna vytvořeného některými funkcemi LSP
vkládací Tab zavolání funkce omni completion
  au BufRead,BufNewFile *.py map <F12> <C-W><C-Z>
  au BufRead,BufNewFile *.py imap <F12> <C-O><C-W><C-Z>
  au BufRead,BufNewFile *.py imap <Tab> <C-X><C-O>

V předposlední části skriptu provádíme dvě operace. První z nich se zvýraznění těch částí textu na řádku, které přesahují 100 znaků. Samozřejmě si můžete tuto hodnotu změnit a použít například konzervativnějších 80 znaků atd. (opět podle nastavené „štábní kultury“). Druhá operace nastavuje funkci volanou při omni completion:

  au BufRead,BufNewFile *.py highlight OverLength ctermbg=yellow ctermfg=white guibg=#592929
  au BufRead,BufNewFile *.py match OverLength /\%99v.\+/
  au BufRead,BufNewFile *.py setlocal omnifunc=lsp#complete

Poslední část skriptu spustí Python Language Server, samozřejmě s testem, zda vůbec existuje spustitelný soubor pyls:

if (executable('pyls'))
    au User lsp_setup call lsp#register_server({
        \ 'name': 'pyls',
        \ 'cmd': {server_info->['pyls']},
        \ 'whitelist': ['python']
        \ })
endif

20. Odkazy na Internetu

  1. Langserver.org
    https://langserver.org/
  2. Language Server Protocol
    https://microsoft.github.io/language-server-protocol/
  3. Language Server Protocol Specification
    https://microsoft.github.io/language-server-protocol/specification
  4. Implementations Language Servers
    https://microsoft.github.io/language-server-protocol/implementors/servers
  5. JSON-RPC 2.0 Specification
    https://www.jsonrpc.org/specification
  6. Why You Should Know the Language Server Protocol
    https://tomassetti.me/what-is-the-language-server-protocol/
  7. Language Server Protocol: A Language Server For DOT With Visual Studio Code
    https://tomassetti.me/language-server-dot-visual-studio/
  8. Python Language Server
    https://github.com/palantir/python-language-server
  9. Jedi – an awesome autocompletion/static analysis library for Python
    https://github.com/davidhalter/jedi
  10. What is lsp
    https://www.reddit.com/r/vim/com­ments/7lnhrt/which_lsp_plu­gin_should_i_use/
  11. Vim-lsp
    https://github.com/prabirshrestha/vim-lsp
  12. Using LSP & clangd in Vim
    https://jonasdevlieghere.com/vim-lsp-clangd/
  13. Seriál Textový editor Vim jako IDE
    https://www.root.cz/serialy/textovy-editor-vim-jako-ide/
  14. Seriál VIM na plný výkon
    https://www.root.cz/serialy/vim-na-plny-vykon/
  15. What about a Common Debug Protocol?
    https://kichwacoders.com/2017/07/28/what-about-a-common-debug-protocol/
  16. Example – Debug Adapter
    https://code.visualstudio­.com/docs/extensions/exam­ple-debuggers
  17. Konfigurační volba complete
    http://vimdoc.sourceforge­.net/htmldoc/options.html#‚com­plete‘
  18. Konfigurační volba completeopt
    http://vimdoc.sourceforge­.net/htmldoc/options.html#‚com­pleteopt‘
  19. Make Vim completion popup menu work just like in an IDE
    http://vim.wikia.com/wiki/Ma­ke_Vim_completion_popup_me­nu_work_just_like_in_an_I­DE
  20. Novinky ve Vimu 7.0
    http://vimdoc.sourceforge­.net/htmldoc/version7.html#new-omni-completion
  21. IndentTab
    https://github.com/vim-scripts/IndentTab
  22. Smart-Tabs
    https://github.com/vim-scripts/Smart-Tabs
  23. Omni completion
    http://vim.wikia.com/wiki/Om­ni_completion
  24. Smart mapping for tab completion
    http://vim.wikia.com/wiki/Smar­t_mapping_for_tab_completi­on
  25. Make Vim completion popup menu work just like in an IDE
    http://vim.wikia.com/wiki/Ma­ke_Vim_completion_popup_me­nu_work_just_like_in_an_I­DE
  26. The LLDB Debugger
    https://lldb.llvm.org/
  27. GDB: The GNU Project Debugger
    https://www.gnu.org/software/gdb/
  28. gdbgui 0.7.8.3: browser-based gdb frontend using Flask and JavaScript to visually debug C, C++, Go, or Rust
    https://pypi.python.org/pypi/gdbgui
  29. Repositář projektu gdbgui
    https://github.com/cs01/gdbgui
  30. gdbgui – examples
    https://github.com/cs01/gdbgu­i/tree/master/examples
  31. Debuggery a jejich nadstavby v Linuxu
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  32. Debuggery a jejich nadstavby v Linuxu (2. část)
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  33. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  34. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  35. Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/
  36. Visual Debugging with DDD
    http://www.drdobbs.com/tools/visual-debugging-with-ddd/184404519
  37. Pydb – Extended Python Debugger
    http://bashdb.sourceforge.net/pydb/
  38. Debugging
    http://janus.uclan.ac.uk/pa­gray/labs/debug.htm
  39. Insight
    http://www.sourceware.org/insight/
  40. Using Language Servers to Edit Code in the Eclipse IDE
    https://www.eclipse.org/com­munity/eclipse_newsletter/2017/ma­y/article3.php
  41. Eclipse LSP4E
    https://projects.eclipse.or­g/projects/technology.lsp4e
  42. Textový editor Vim 8 (nejenom) ve Fedoře
    https://mojefedora.cz/textovy-editor-vim-8-nejenom-ve-fedore/
  43. Textový editor Vim 8 – změny ve skriptovacím engine Vimu
    https://mojefedora.cz/textovy-editor-vim-8-zmeny-ve-skriptovacim-engine-vimu/
  44. Textový editor Vim 8 – dokončení popisu novinek ve skriptovacím engine
    https://mojefedora.cz/textovy-editor-vim-8-dokonceni-popisu-novinek-ve-skriptovacim-engine/
  45. Sample code illustrating the VS Code extension API
    https://github.com/Microsoft/vscode-extension-samples
  46. PEP 8: Tabs or Spaces?
    https://www.python.org/dev/peps/pep-0008/#tabs-or-spaces
  47. Eclipse Next-Generation IDE
    http://www.eclipse.org/che/
  48. Atom IDE
    https://ide.atom.io/
  49. Atom Language Server Protocol Client
    https://github.com/atom/atom-languageclient
  50. Atom: moderní textový editor
    https://www.root.cz/clanky/atom-moderni-textovy-editor/

Autor článku

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