Plugin do cliente LSP

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.

Estrutura do menu

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.

Cliente LSPIr para definição

[textDocument/definition] Vai para a definição do símbolo atual.

Cliente LSPIr para declaração

[textDocument/declaration] Vai para a declaração do símbolo atual.

Cliente LSPIr para definição de tipo

[textDocument/typeDefinition] Vai para a definição de tipo do símbolo atual.

Cliente LSPEncontrar referências

[textDocument/references] Encontra referências para o símbolo atual.

Cliente LSPEncontrar implementações

[textDocument/implementation] Encontra implementações do símbolo atual.

Cliente LSPRealce

[textDocument/documentHighlight] Realça as referências de símbolos atuais no documento atual.

Cliente LSP ClientSobre

[textDocument/hover] Ativa a exibição de informações ao passar o cursor sobre o símbolo atual.

Cliente LSPFormato

[textDocument/formatting] [textDocument/rangeFormatting] Formata o documento atual ou a seleção atual.

Cliente LSPRenomear

[textDocument/rename] Renomeia o símbolo atual.

Cliente LSPCorreção rápida

[textDocument/codeAction, workspace/executeCommand] Calcula e aplica uma correção rápida para um diagnóstico na posição (ou linha) atual.

Cliente LSPMostrar documentação de complementação da seleção

Mostra a documentação para o item selecionado na lista de completamento.

Cliente LSPAtivar ajuda de assinatura com completamento automático

Mostra também a ajuda para assinatura na lista de completamento.

Cliente LSPIncluir declaração nas referências

Solicita a inclusão da declaração de um símbolo ao solicitar referências.

Cliente LSPAdiciona parênteses com a função de completamento

Adiciona automaticamente um par de parênteses após o completamento de uma função.

Cliente LSPMostrar informações sobre

Mostra informações ao passar o cursor do mouse sobre o elemento. Independentemente dessa configuração, a solicitação sempre poderá ser iniciada manualmente.

Cliente LSPFormatar ao digitar

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

Cliente LSPSincronização de documento incremental

Envia edições parciais do documento para atualizar o servidor em vez do documento de texto inteiro (se suportado).

Cliente LSPDestacar localização ir para

Fornece uma indicação visual temporária após executar um comando "goto" para um local (de definição, declaração, etc.).

Cliente LSPMostrar notificações de diagnósticos

[textDocument/publishDiagnostics] Processa e exibe notificações de diagnóstico enviadas pelo servidor.

Cliente LSPMostrar destaques de diagnósticos

Adiciona destaques de texto para os intervalos indicados nos diagnósticos.

Cliente LSPMostrar marcas de diagnóstico

Adiciona marcas de documento para as linhas indicadas no diagnóstico.

Cliente LSPAlternar para aba de diagnóstico

Acessa a aba de diagnóstico na visualização de ferramentas do plugin.

Cliente LSPFechar todas as abas que não sejam de diagnóstico

Fecha todas as abas que não sejam de diagnóstico (por exemplo referências) na visualização da ferramentas do plugin.

Cliente LSPReiniciar o servidor LSP

Reinicia o servidor LSP do documento atual.

Cliente LSPReiniciar todos os servidores LSP

Para todos os servidores LSP, que serão então (re)iniciados conforme necessário.

Suporte ao símbolo de "goto"

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.

Configurando o contorno de símbolo do cliente LSP

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.

Suporte ao símbolo "goto" global

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.

Outras funcionalidades

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.

Configuração

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 .kateproject substitui a acima

  • a 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.

Configurações do servidor LSP

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.

Formatar o servidor LSP ao salvar

Você pode ativar a opção "formatar ao salvar" nas configurações do LSP, na janela de configuração.

Supressão de diagnóstico de servidor LSP

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.

Solução de problemas com o servidor LSP

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.

Configuração do ambiente de execução

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 hostname quanto o prefix definem o ambiente de execução (o primeiro pelo nome, o segundo pelo conteúdo). Em uma instância de processo de editor, o mesmo hostname não deve ser associado a prefix diferentes, pois isso leva a um comportamento indefinido (sem necessidade de diagnóstico).

  • Tanto prefix quanto pathMappings estã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 como lspclient e KATE_EXEC_SERVER é definido com o ID do servidor (por exemplo, python).

  • Em particular, também KATE_EXEC_INSPECT é definido como 1. 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).