O plugin do cliente LSP fornece muitos funcionalidades de linguagem, como preenchimento automático de código, navegação de código ou localização de referências com base no Protocolo do Servidor de Linguagem.
Depois de ativar o cliente LSP na página do plugin, uma nova página chamada Cliente LSP aparecerá na sua janela de configuração do Kate.
Se apropriado, um comando LSP correspondente também é mencionado na explicação abaixo, cuja documentação pode então fornecer informações adicionais e interpretação, embora possa variar dependendo da linguagem específica. A expressão 'símbolo atual' refere-se ao símbolo correspondente à posição atual do cursor, conforme determinado pela linguagem e pela implementação do servidor.
- →
[textDocument/definition] Vai para a definição do símbolo atual.
- →
[textDocument/declaration] Vai para a declaração do símbolo atual.
- →
[textDocument/typeDefinition] Vai para a definição de tipo do símbolo atual.
- →
[textDocument/references] Encontra referências para o símbolo atual.
- →
[textDocument/implementation] Encontra implementações do símbolo atual.
- →
[textDocument/documentHighlight] Realça as referências de símbolos atuais no documento atual.
- →
[textDocument/hover] Ativa a exibição de informações ao passar o cursor sobre o símbolo atual.
- →
[textDocument/formatting] [textDocument/rangeFormatting] Formata o documento atual ou a seleção atual.
- →
[textDocument/rename] Renomeia o símbolo atual.
- →
[textDocument/codeAction, workspace/executeCommand] Calcula e aplica uma correção rápida para um diagnóstico na posição (ou linha) atual.
- →
Mostra a documentação para o item selecionado na lista de completamento.
- →
Mostra também a ajuda para assinatura na lista de completamento.
- →
Solicita a inclusão da declaração de um símbolo ao solicitar referências.
- →
Adiciona automaticamente um par de parênteses após o completamento de uma função.
- →
Mostra informações ao passar o cursor do mouse sobre o elemento. Independentemente dessa configuração, a solicitação sempre poderá ser iniciada manualmente.
- →
[document/onTypeFormatting] Formata partes do documento ao digitar determinados caracteres de gatilho. Por exemplo, isso pode aplicar recuo em novas linhas, ou conforme determinado pelo Servidor LSP. Observe que os scripts de recuo do editor podem tentar fazer o mesmo (dependendo do modo) e, portanto, pode não ser recomendável ter ambos ativados ao mesmo tempo.
- →
Envia edições parciais do documento para atualizar o servidor em vez do documento de texto inteiro (se suportado).
- →
Fornece uma indicação visual temporária após executar um comando "goto" para um local (de definição, declaração, etc.).
- →
[textDocument/publishDiagnostics] Processa e exibe notificações de diagnóstico enviadas pelo servidor.
- →
Adiciona destaques de texto para os intervalos indicados nos diagnósticos.
- →
Adiciona marcas de documento para as linhas indicadas no diagnóstico.
- →
Acessa a aba de diagnóstico na visualização de ferramentas do plugin.
- →
Fecha todas as abas que não sejam de diagnóstico (por exemplo referências) na visualização da ferramentas do plugin.
- →
Reinicia o servidor LSP do documento atual.
- →
Para todos os servidores LSP, que serão então (re)iniciados conforme necessário.
O cliente LSP pode ajudá-lo a navegar até qualquer símbolo em seu projeto ou arquivo atual. Para navegar até qualquer símbolo no arquivo, use a ferramenta "Contorno de símbolo do cliente LSP" na borda direita do Kate. Esta ferramenta lista todos os símbolos encontrados pelo servidor no documento atual.
Por padrão, os símbolos são classificados de acordo com sua ocorrência no documento, mas você pode alterar a classificação para ordem alfabética. Para isso, clique com o botão direito do mouse na janela de ferramentas e marque "Classificar alfabeticamente".
A visualização de ferramentas mostra os símbolos no modo de árvore por padrão, porém você pode alterá-la para uma lista usando o menu de contexto.
Para ir para qualquer símbolo em seu projeto, você pode abrir a caixa de diálogo do símbolo "Ir para" usando Ctrl+Alt+p. A caixa de diálogo está vazia quando é aberta, mas assim que você digita algo, ela começará a mostrar os símbolos correspondentes. A qualidade das correspondências, bem como os recursos de filtragem, dependem do servidor que você usa. Por exemplo, o clangd oferece suporte à filtragem aproximada, mas alguns outros servidores podem não oferecer.
O comando de troca de cabeçalho de origem do Clangd é suportado. Para trocar o cabeçalho de origem em um projeto C ou C++, use a opção "Trocar cabeçalho de origem" no menu de contexto ou o atalho F12.
Você pode ir rapidamente para um símbolo colocando o mouse sobre ele e pressionando Ctrl + botão esquerdo do mouse.
A página de configuração do plugin permite, em sua maioria, a configuração persistente de alguns dos itens de menu acima. No entanto, há uma entrada adicional para especificar o arquivo de configuração do servidor. Este é um arquivo JSON que pode ser usado para especificar o servidor LSP a ser iniciado (e, em seguida, para se comunicar com ele via stdin/stdout). Para maior conveniência, algumas configurações padrão estão incluídas, as quais podem ser inspecionadas na página de configuração do plugin. Para auxiliar na explicação abaixo, um trecho dessa configuração é fornecido aqui:
{
"servers": {
"bibtex": {
"use": "latex",
"highlightingModeRegex": "^BibTeX$"
},
"c": {
"command": ["clangd", "-log=error", "--background-index"],
"commandDebug": ["clangd", "-log=verbose", "--background-index"],
"url": "https://clang.llvm.org/extra/clangd/",
"highlightingModeRegex": "^(C|ANSI C89|Objective-C)$"
},
"cpp": {
"use": "c",
"highlightingModeRegex": "^(C\\+\\+|ISO C\\+\\+|Objective-C\\+\\+)$"
},
"d": {
"command": ["dls", "--stdio"],
"url": "https://github.com/d-language-server/dls",
"highlightingModeRegex": "^D$"
},
"fortran": {
"command": ["fortls"],
"rootIndicationFileNames": [".fortls"],
"url": "https://github.com/hansec/fortran-language-server",
"highlightingModeRegex": "^Fortran.*$"
},
"javascript": {
"command": ["typescript-language-server", "--stdio"],
"rootIndicationFileNames": ["package.json", "package-lock.json"],
"url": "https://github.com/theia-ide/typescript-language-server",
"highlightingModeRegex": "^JavaScript.*$",
"documentLanguageId": false
},
"latex": {
"command": ["texlab"],
"url": "https://texlab.netlify.com/",
"highlightingModeRegex": "^LaTeX$"
},
"go": {
"command": ["go-langserver"],
"commandDebug": ["go-langserver", "-trace"],
"url": "https://github.com/sourcegraph/go-langserver",
"highlightingModeRegex": "^Go$"
},
"python": {
"command": ["python3", "-m", "pyls", "--check-parent-process"],
"url": "https://github.com/palantir/python-language-server",
"highlightingModeRegex": "^Python$"
},
"rust": {
"command": ["rls"],
"path": ["%{ENV:HOME}/.cargo/bin", "%{ENV:USERPROFILE}/.cargo/bin"],
"rootIndicationFileNames": ["Cargo.lock", "Cargo.toml"],
"url": "https://github.com/rust-lang/rls",
"highlightingModeRegex": "^Rust$"
},
"ocaml": {
"command": ["ocamlmerlin-lsp"],
"url": "https://github.com/ocaml/merlin",
"highlightingModeRegex": "^Objective Caml.*$"
}
}
}
Observe que cada "command" pode ser uma matriz ou uma string (nesse caso, ele é dividido em uma matriz). Além disso, uma entrada "global" de nível superior (ao lado de "server") também é considerada (veja mais abaixo). O binário especificado é procurado da maneira usual, por exemplo, usando PATH. Se ele estiver instalado em um local personalizado, este último pode precisar ser estendido. Ou, alternativamente, um link simbólico ou um script wrapper pode ser usado em um local que esteja dentro do PATH usual. Como ilustrado acima, também é possível especificar um "caminho" que será procurado após os locais padrão.
Todas as entradas em "command", "root" e "path" estão sujeitas à expansão de variáveis.
A expressão regular "highlightingModeRegex" é usada para mapear o modo de realce usado pelo Kate ao ID da linguagem do servidor. Se nenhuma expressão regular for fornecida, o próprio ID da linguagem será usado. Se uma entrada "documentLanguageId" for definida como falsa, nenhum ID de linguagem será fornecido ao servidor ao abrir o documento. Isso pode apresentar melhores resultados para alguns servidores que são mais precisos na determinação do tipo de documento do que fazê-lo com base em um modo do Kate.
A partir do exemplo acima, a ideia principal provavelmente está clara. Além disso, cada objeto de entrada do servidor também pode ter uma entrada "initializationOptions", que é passada para o servidor como parte do método 'initialize'. Se presente, uma entrada "settings" é passada para o servidor por meio da notificação 'workspace/didChangeConfiguration'. "completionTriggerCharacters" ou "signatureTriggerCharacters" podem ser especificados como um objeto JSON com os membros de string "exclude" e/ou "include". Estes serão usados para excluir ou adicionar caracteres ao conjunto de gatilhos correspondente, conforme fornecido pelo servidor.
São aplicadas várias etapas de sobreposição/fusão;
configuração do usuário (carregada de um arquivo) substitui a configuração padrão (interna)
entrada "lspclient" na configuração do projeto
.kateprojectsubstitui a acimaa entrada "global" resultante é usada para complementar (não substituir) qualquer entrada do servidor
Uma instância de servidor é usada para cada combinação (raiz, tipo de servidor). Se "root" for especificado como um caminho absoluto, ele será usado como está; caso contrário, será relativo ao “projectBase” (conforme determinado pelo plugin de projeto), se aplicável, ou, caso contrário, relativo ao diretório do documento. Se não for especificado e "rootIndicationFileNames" for uma matriz de nomes de arquivos, um diretório pai do documento atual que contenha tal arquivo será selecionado. Alternativamente, se "root" não for especificado e "rootIndicationFilePatterns" for uma matriz de padrões de arquivo, um diretório pai do documento atual que corresponda ao padrão de arquivo será selecionado. Como último recurso, o diretório inicial é selecionado como "root". Para qualquer documento, o "root" resultante determina se uma instância separada é necessária ou não. Nesse caso, o "root" é passado como rootUri/rootPath.
Em geral, recomenda-se deixar o diretório raiz sem especificação, pois não é tão importante para um servidor (embora isso possa variar). Menos instâncias de servidor são obviamente mais eficientes e também têm uma visão mais ampla do que a visão de muitas instâncias separadas.
Como mencionado acima, várias entradas estão sujeitas à expansão de variáveis. Uma aplicação adequada disso, combinada com abordagens de "script wrapper", permite a personalização para uma grande variedade de circunstâncias. Por exemplo, considere um cenário de desenvolvimento em Python que consiste em múltiplos projetos (por exemplo, repositórios Git), cada um com sua própria configuração de ambiente virtual. Usando a configuração padrão, o servidor de linguagem Python não reconhecerá o ambiente virtual. No entanto, isso pode ser corrigido com a seguinte abordagem. Primeiro, o seguinte fragmento pode ser inserido nas "Configurações do servidor do usuário" do plugin LSPClient:
{
"servers":
{
"python":
{
"command": ["pylsp_in_env", "%{Project:NativePath}"],
"root": "."
}
}
}
A entrada raiz acima é relativa ao diretório do projeto e garante que um servidor de linguagem separado seja iniciado para cada projeto, o que é necessário neste caso, pois cada um tem um ambiente virtual distinto.
pylsp_in_env é um pequeno "script auxiliar" que deve ser colocado em PATH com o seguinte conteúdo (a ser ajustado):
#!/bin/bash cd $1 # executa o servidor (python-lsp-server) dentro de um virtualenv # (i.e. com configuração de variáveis virtualenv) # então ative o virtualenv source XYZ # disponibilidade do servidor ou os argumentos podem variar exec meuservidor
Este é apenas um exemplo de um padrão mais geral que pode ser tratado de forma um pouco mais confortável, conforme descrito na seção Ambiente de execução abaixo.
Cada servidor LSP específico possui sua própria forma de personalização e pode usar meios específicos de linguagem/ferramenta para configuração, por exemplo tox.ini (entre outros, para Python) ou .clang-format para formato no estilo C++. Essa configuração também pode ser usada por outras ferramentas (não LSP), como tox ou clang-format. Além disso, alguns servidores LSP também carregam configurações de arquivos personalizados (por exemplo .ccls). Ademais, a configuração personalizada do servidor também pode ser passada pelo LSP (protocolo), conforme mencionado nas entradas "initializationOptions" e "settings" na configuração do servidor.
Como são aplicados vários níveis de sobrescrita/fusão, o exemplo a seguir de configuração de cliente especificada pelo usuário ajusta algumas configurações do servidor de linguagem Python.
{
"servers": {
"python": {
"settings": {
"pyls": {
"plugins": {
"pylint": {
"enable": true
}
}
}
}
}
}
}
Infelizmente, a configuração/personalização do servidor LSP muitas vezes não é tão bem documentada, de forma que apenas a análise do código-fonte revela as abordagens de configuração e o conjunto de opções disponíveis. Em particular, o servidor do exemplo acima suporta muito mais opções em "configurações". Consulte a documentação de outro cliente LSP para vários outros exemplos de servidores de linguagem e configurações correspondentes, que podem ser facilmente transformadas na configuração JSON usada aqui e descrita acima.
Você pode ativar a opção "formatar ao salvar" nas configurações do LSP, na janela de configuração.
Pode acontecer de serem relatados diagnósticos que não sejam muito úteis. Isso pode ser bastante trabalhoso, especialmente se houver muitos (frequentemente do mesmo tipo). Em alguns casos, isso pode ser ajustado por meios específicos da linguagem (servidor). Por exemplo, o mecanismo de configuração do clangd permite o ajuste de alguns aspectos dos diagnósticos. Em geral, no entanto, pode nem sempre ser evidente como fazer isso, ou pode até não ser possível da maneira desejada devido a limitações ou bugs do servidor.
Assim, o plugin suporta a supressão de diagnósticos semelhante, por exemplo, às supressões do Valgrind. A configuração mais detalhada pode ser fornecida em uma chave "suppressions" na configuração JSON (mesclada).
{
"servers": {
"c": {
"suppressions": {
"rulename": ["filename", "foo"],
"clang_pointer": ["", "clang-tidy", "clear_pointer"],
}
}
}
}
Cada regra (válida) tem um nome arbitrário e é definida por uma matriz de comprimento 2 ou 3 que fornece uma expressão regular para corresponder ao nome do arquivo (completo), uma expressão regular para corresponder ao diagnóstico (texto) e uma expressão regular opcional que corresponde ao intervalo do código-fonte do texto ao qual o diagnóstico se aplica.
Além da configuração detalhada acima, o menu de contexto na aba de diagnósticos também permite adicionar/remover supressões que correspondam exatamente a um diagnóstico específico (texto), seja globalmente (qualquer arquivo) ou localmente (o arquivo específico em questão). Essas supressões são armazenadas e carregadas da configuração da sessão.
Uma coisa é descrever como configurar um servidor LSP (personalizado) para qualquer linguagem específica, outra é conseguir que o servidor funcione sem problemas. Normalmente, felizmente, este último é o caso mais comum. Às vezes, porém, podem surgir problemas devido a alguma configuração incorreta "boba" ou a um problema mais fundamental com o próprio servidor. Este último pode se manifestar tipicamente como algumas tentativas de iniciar o servidor, conforme relatado na aba Saída. Esta última, no entanto, destina-se apenas a transmitir mensagens ou progresso de alto nível, e não a fornecer diagnósticos detalhados, e muito menos para o que é, na verdade, outro processo (o servidor LSP).
A maneira usual de diagnosticar isso é adicionar alguma(s) flag(s) ao comando de inicialização (do servidor de linguagem) que habilite(m) o registro (adicional) em algum arquivo ou no erro padrão, caso isso não ocorra por padrão. Se o Kate for iniciado na linha de comando, então poderemos obter mais informações sobre o que pode estar dando errado.
Também pode ser informativo examinar a troca de mensagens do protocolo entre o cliente LSP do Kate e o servidor LSP. Novamente, este último geralmente possui maneiras de rastrear isso. O cliente LSP também fornece rastreamento de depuração adicional (para stderr) quando o Kate é invocado com a seguinte opção LSPCLIENT_DEBUG=1 e devidamente exportado.
O exemplo de virtualenv em Python acima é apenas um exemplo de um "ambiente de execução" que opera de forma distinta e separada do ambiente usual do host. Isso pode ser alcançado por meio de diferentes configurações de variáveis (por exemplo, virtualenv), ou uma configuração (s)chroot (alternando para outro diretório como novo root), um contêiner (por exemplo, podman, docker) ou uma sessão ssh para outro host. Em cada caso, o "outro ambiente" é definido por um "prefixo de execução". Ou seja, algum programa pode ser invocado/executado no outro ambiente por meio de um "prefixo" (um programa e argumentos) anexado à invocação pretendida. Por exemplo, podman exec -i nome_do_contêiner ou ssh usuário@host.
Em particular, como no exemplo anterior do virtualenv, pode-se optar/precisar executar um servidor LSP em um ambiente separado (por exemplo, um contêiner com todas as dependências necessárias para algum projeto). A abordagem "manual" descrita acima tem a desvantagem de substituir a linha de comando padrão do LSP, que precisa ser especificada e duplicada novamente no script wrapper. Além disso, em alguns dos outros exemplos mencionados acima, o "path namespace" do host (como visto pelo editor) pode ser diferente do do ambiente. Para lidar com essas questões de forma mais sistemática (do que uma abordagem "personalizada"), algumas configurações adicionais podem ser especificadas.
Por exemplo, o seguinte pode ser especificado em uma configuração .kateproject. Obviamente, os comentários "falsos" não devem ser incluídos.
{
// isto pode ser uma array de objetos
"exec": {
"hostname": "foobar"
// o comando também pode ser uma array de string
"prefix": "podman exec -i foobarcontainer",
"mapRemoteRoot": true,
"pathMappings": [
// todas as formas a seguir são possíveis
// um alternativa mais automágica existe também, veja a seguir/abaixo
[ "/dir/on/host", "/mounted/in/container" ]
{ "localRoot": "/local/dir", "remoteRoot": "/remote/dir" }
]
},
"lspclient": {
"servers": {
"python": {
// isto irá corresponder/juntar com o objeto acima
"exec": { "hostname": "foobar" },
// confina este servidor a sua raiz de projeto,
// então ele não é usado para outros projetos que podem ser abertos
// (outros servidores podem já empregar raízes específicas, mas o python generalmente não pode)
"root": "."
},
"c": {
// as above
"exec": { "hostname": "foobar" },
"root": "."
}
}
}
}
Então, o que acontece como resultado do exposto acima? Como mencionado, a parte lspclient acima é mesclada à configuração global, portanto, uma seção exec é encontrada (para as linguagens especificadas). Uma busca é realizada por outro objeto (que especifica hostname correspondente) na seção exec ou lspclient, e um objeto correspondente serve como base para uma mesclagem. Como resultado, um servidor LSP (para C e Python) terá sua linha de comando anexada ao prefixo especificado (variável substituída) e, portanto, será iniciado dentro do contêiner fornecido. Obviamente, o contêiner deve ter sido criado, estar em um estado iniciado corretamente e equipado com servidores LSP apropriados. Poderia ter havido a tentação de usar a seção global (dentro de lspclient). Isso também poderia funcionar, mas então todos os servidores LSP seriam iniciados com esse prefixo, incluindo aqueles para, por exemplo, Markdown, script Bash ou JSON. É mais provável que o host usual ainda forneça esses (se estiverem em uso). Portanto, como sempre, depende da sua configuração específica.
No entanto, o servidor LSP agora pode observar caminhos (remotos) diferentes dos caminhos (locais) que são vistos e usados pelo editor. Os pathMappings especificados são usados para traduzir de um lado para o outro na comunicação com o servidor LSP. Isso é, obviamente, o máximo possível. Claramente, nem todos os caminhos locais têm uma representação remota, mas os ausentes também não são vistos (pelo servidor) e não representam um problema. Por outro lado, o servidor (remoto) agora pode ver e fornecer referências na "raiz remota" que não são evidentemente/facilmente representadas no sistema local. O mapRemoteRoot habilitado mapeia implicitamente a raiz remota para uma "URL local" exec://foobar, que é então tratada por um protocolo KIO simples. Este último utiliza essencialmente (por exemplo) podman exec -i foobarcontainer cat somefile para copiar de um arquivo remoto para um local (e outras variações semelhantes usando ferramentas do pacote coreutils). Basta dizer que não se destina ao uso geral e não promete qualquer desempenho, mas é suficiente para carregar um arquivo referenciado no editor de forma rápida e fácil.
Agora é fácil perceber que uma versão simplificada da configuração acima (sem hostname ou pathMappings) poderia ser usada para lidar com o ambiente virtual sem precisar duplicar a linha de comando do servidor (no script wrapper).
O que se segue pode não ser tão facilmente visível, por isso é mencionado aqui explicitamente.
Tanto o
hostnamequanto oprefixdefinem o ambiente de execução (o primeiro pelo nome, o segundo pelo conteúdo). Em uma instância de processo de editor, o mesmohostnamenão deve ser associado aprefixdiferentes, pois isso leva a um comportamento indefinido (sem necessidade de diagnóstico).Tanto
prefixquantopathMappingsestão sujeitos à expansão de variáveis (do editor) (mas leve em consideração o item anterior).Em tempo de execução, algumas variáveis de ambiente também são definidas, as quais podem ser usadas para ajustar (sutilmente) o comportamento do "inicializador de prefixo" (embora, novamente, preste atenção ao primeiro item). Em particular,
KATE_EXEC_PLUGINé definido comolspclienteKATE_EXEC_SERVERé definido com o ID do servidor (por exemplo,python).Em particular, também
KATE_EXEC_INSPECTé definido como1. Isso notifica o "inicializador de prefixo" de que o receptor (plugin Kate) aceita alguns dados fora de banda/protocolo. Isso permite que o inicializador use algum meio para determinar mapeamentos de caminho (por exemplo,podman inspect) e comunique isso em um formato adequado. Isso será então removido do fluxo que, de outra forma, está em conformidade com o protocolo LSP e usado para estender qualquer mapeamento de caminho definido. Isso pode servir como uma alternativa para especificar, por exemplo, montagens de ligação explicitamente (novamente, duplicando a definição do contêiner). Concretamente, o trecho acima poderia então ser usado em vez disso;{ // ... "exec": { "hostname": "foobar" // repetição infeliz do nome, mas o script auxiliar é simples e direto. "prefix": "exec_inspect.sh foobarcontainer podman exec -i foobarcontainer", "mapRemoteRoot": true, "pathMappings": [] } // ... }
Por último, mas não menos importante, e se a abordagem "KIO de fallback" não for considerada adequada? Na prática, muitas vezes é possível "montar" a raiz remota no sistema de arquivos local e, em seguida, especificar este último em um pathMappings. Para começar, o conhecido sistema de arquivos sshfs (fuse) pode montar um sistema (realmente) remoto no local. Para um contêiner (podman/docker), a maioria das configurações (de driver de sistema de arquivos) suporta o seguinte truque;
$ rootdir=/proc/`podman inspect --format '{{.State.Pid}}' containername`/root
# alguns links simbólicos podem causar problemas, veja também a alternativa abaixo
$ sudo mount --bind $rootdir /somewhere/containername
Se isso falhar, pode-se recorrer ao sshfs, ou, na verdade, a um subconjunto dele para montar a raiz remota/do contêiner;
# consulte as respectivas páginas de manual; sftp-server é suficiente para o protocolo de operações de arquivo real (não criptografado) $ socat 'exec:podman exec -i containername /usr/lib/openssh/sftp-server' 'exec:sshfs -o transform_symlinks -o passive \:/ /somewhere/containername' # ... $ fusermount -u /somewhere/containername
Observe que essa montagem da raiz remota no sistema de arquivos do host provavelmente será especificada manualmente e explicitamente em uma seção pathMappings (exceto em um inicializador de prefixo muito inteligente que organiza tudo isso automaticamente).