Uma das virtudes do VS Code é como ele integra-se facilmente com diferentes linguagens. O esforço é pequeno, geralmente resumindo-se à instalação de um único plugin. Essa característica o torna uma ferramenta extremamente produtiva, e que vem conquistando o coração dos desenvolvedores ao redor do mundo.
O curioso é que o VS Code utiliza um protocolo aberto para ter esse "jeitão" de IDE, chamado Language Server Protocol, que entre outras funcionalidades traz ao editor a possibilidade de autocomplete, go to definition, diagnosis e documentation.
É... Microsoft popularizando protocolos abertos. Tempos interessantes esses que vivemos, não?
Durante o desenvolvimento do VS Code, a Microsoft introduziu o Language Server Protocol. O langserver.org o define da seguinte forma:
The Language Server protocol is used between a tool (the client) and a language smartness provider (the server) to integrate features like auto complete, go to definition, find all references and alike into the tool.
Em outras palavras, é um protocolo que descreve a comunicação entre um cliente (um editor de textos, por exemplo) e um servidor (ligado à uma determinada linguagem), com a motivação de proporcionar e enriquecer ferramentas de desenvolvimento.
Ou seja, podemos ter uma solução única para Python, que traga as mesmas funcionalidades para diferentes editores ou IDEs. Em contrapartida, podemos ter um único editor que suporte diferentes linguagens e tais funcionalidades avançadas. O langserver.org chama esse problema de "The Matrix Problem":
Como afirma o site oficial do projeto:
LSP is a win for both language providers and tooling vendors!
Na prática, o que acontece é uma comunicação inter-processos entre o language server e a ferramenta de desenvolvimento. O protocolo utilizado para tal operação é o JSON-RPC, um protocolo simples, e com um formato de dados extremamente popular.
Nem toda linguagem pode suportar todas as funcionalidade definidas pelo protocolo (o mesmo vale para as ferramentas de desenvolvimento), mas isso não impede a sua adoção. O protocolo permite que tanto cliente como servidor "anunciem" suas capacidades, portanto, as duas pontas podem ter um comportamento preventivo quando determinada capacidade (como go to definition) não é suportada.
Para Python, além do language server implementado pela própria Microsoft e usado no VS Code, temos o Python Language Server. Ele conta com as seguintes funcionalidades:
O que acho mais interessante em relação ao projeto é que ele não reinventa a roda. Por exemplo, ele não implementa uma nova ferramenta de linting, pelo contrário, ele utiliza ferramentas que já são bem conhecidas pela comunidade:
Há um certo grau de customização, como por exemplo usar o Black ao invés de YAPF. Discutiremos mais sobre esse tópico a seguir.
Segundo o langserver.org, os principais IDEs e editores já possuem alguma forma de cliente implementado. Seja através de extensões (como JetBrains IDE, Vim, Sublime, Atom e Emacs), ou através de suporte built-in (como VS Code e Oni).
Para Vim, uma opção que tenho usado com sucesso é o LanguageClient-neovim, que embora possa parecer exclusivo para o Neovim, possui suporte para Vim 8.x.
Vamos para um exemplo prático de como usar LSP e Vim com Python. Mas antes, deixa eu definir algumas constraints do meu ambiente de desenvolvimento:
Para começar, vamos instalar o language server. Eu prefiro tê-lo dentro do ambiente virtual do projeto em que estou trabalhando. É trabalhoso e repetitivo se você estiver trabalhando em diferentes projetos e virtualenvs! Uma alternativa mais prática é tê-lo instalado em uma instância global do pyenv. Fica a seu critério o destino da instalação:
$ pip install python-language-server
O próprio servidor identificará quais ferramentas você tem instaladas e habilitará determinada capacidade proporcionada por tal ferramenta (por exemplo, se o Rope estiver instalado, será capaz de fazer renaming). Mas dentre elas o Jedi é fundamental, e por isso é instalada automaticamente como dependência do PyLS.
Nesse momento temos grande parte das funcionalidades cobertas pelo LSP habilitadas pelo servidor. Uma muito útil, que ainda está faltando, é o renaming. Segundo a lista acima, conseguimos tal capacidade ao instalar o Rope:
$ pip install 'python-language-server[rope]'
Se você está interessado em utilizar a configuração padrão do python-language-server, uma opção mais prática é instalar todos os providers de uma só vez:
$ pip install 'python-language-server[all]'
Para fins didáticos estamos resolvendo as dependências manualmente. Há uma outra forma de gerenciar quais providers serão utilizados, que discutiremos a seguir.
Para termos o isort e o Black habilitados como ferramentas de formatação, precisaremos recorrer à plugins de terceiros:
$ pip install pyls-isort pyls-black
Infelizmente, o pyls-black
exige que você não tenha o autopep8
instalado para funcionar. Como não há um controle manual para decidir qual será usado,
a melhor forma é garantindo que apenas uma das duas extensões esteja instalada.
Temos tudo o que é necessário para o lado do servidor.
A melhor forma de instalarmos o LanguageClient-neovim é através do utilitário Plug
:
" ~/.vimrc
Plug 'autozimu/LanguageClient-neovim', {
\ 'branch': 'next',
\ 'do': 'bash install.sh',
\ }
Plug 'junegunn/fzf'
Ele funciona muito bem com o fzf, portanto, é uma boa pedida termos os dois disponíveis.
O próximo passo é informar ao cliente como encontrar o servidor da linguagem:
" ~/.vimrc
(...)
let g:LanguageClient_serverCommands = {
\ 'python': ['pyls'],
\ }
Ainda precisamos dizer ao LSP sobre as nossas preferências. O arquivo abaixo está situado na raíz do projeto que estou trabalhando,
mais especificamente em ./.vim/settings.json
:
{
"pyls": {
"configurationSources": ["flake8"],
"plugins": {
"yapf": {
"enabled": false
}
}
}
}
Para fins didáticos adicionei o yapf
à configuração. Alguns plugins possuem a flexibilidade de mesmo que instalados, poderem ser desabilitados.
Pronto! O cliente está configurado, e com os seguintes comandos você pode explorar todas as funcionalidades do LSP:
Ctrl+x Ctrl+o
: Auto completion via Omni completion (o Deoplete é uma boa pedida):call LanguageClient#textDocument_definition()
: Go to definition:call LanguageClient#textDocument_hover()
: Hover:call LanguageClient#textDocument_references()
: References:call LanguageClient#textDocument_documentSymbol()
: Lista de Symbols:call LanguageClient#textDocument_rename()
: Renaming:call LanguageClient#textDocument_formatting()
: Formatting:call :call LanguageClient_contextMenu()
: Menu de contexto mostrando todas as opções disponíveisAgora é só configurar os key bindings e correr para o abraço :)