Pergunta

Muitos editores e IDEs ter conclusão de código.Alguns deles são muito "inteligentes" os outros não são realmente.Eu estou interessado na mais inteligentes do tipo.Por exemplo, eu tenho visto IDEs que oferecem apenas uma função, se ele é um) disponíveis no escopo atual b) seu valor de retorno é válido.(Por exemplo, depois de "5 + foo[tab]" ele oferece apenas funções que retornam algo que pode ser adicionado a um número inteiro ou nomes de variáveis do tipo correcto.) Eu vi também que eles colocam mais frequentemente usado ou maior opção frente da lista.

Eu percebo que você precisa para analisar o código.Mas, geralmente, durante a edição do atual código é inválido existem erros de sintaxe em ti.Como você analisa algo quando ele é incompleto e contém erros?

Há também uma restrição de tempo.A conclusão é inútil se ele leva alguns segundos para vir acima com uma lista.Às vezes, a conclusão algoritmo lida com milhares de classes.

Quais são as boas estruturas de dados e algoritmos para isso?

Foi útil?

Solução

O mecanismo Intellisense no meu produto de serviço de idioma UnrealScript é complicado, mas darei uma visão geral da melhor maneira possível. O serviço de idioma C# no VS2008 SP1 é minha meta de desempenho (por um bom motivo). Ainda não está lá, mas é rápido/preciso o suficiente para que eu possa oferecer sugestões com segurança depois que um único caractere é digitado, sem esperar pelo espaço Ctrl+ou o usuário digitando um . (ponto). Quanto mais informações as pessoas [trabalham em serviços de idiomas] obtêm sobre esse assunto, melhor a experiência do usuário final que eu tiver, se eu já usasse seus produtos. Há vários produtos em que tive a infeliz experiência de trabalhar que não prestaram muita atenção aos detalhes e, como resultado, eu estava lutando com o IDE mais do que estava codificando.

No meu serviço de idioma, ele é apresentado como o seguinte:

  1. Obtenha a expressão no cursor. Isso caminha desde o início do Expressão de acesso ao membro até o final do identificador, o cursor acabou. A expressão de acesso ao membro geralmente está no formulário aa.bb.cc, mas também pode conter chamadas de método como em aa.bb(3+2).cc.
  2. Pegue o contexto ao redor do cursor. Isso é muito complicado, porque nem sempre segue as mesmas regras que o compilador (longa história), mas para aqui assume que sim. Geralmente, isso significa obter as informações em cache sobre o método/classe em que o cursor está dentro.
  3. Diga o objeto de contexto implementa IDeclarationProvider, onde você pode ligar GetDeclarations() Para conseguir um IEnumerable<IDeclaration> de todos os itens visíveis no escopo. No meu caso, esta lista contém os habitantes locais/parâmetros (se em um método), membros (campos e métodos, estáticos apenas, a menos que em um método de instância, e nenhum membro privado dos tipos de base), globais (tipos e constantes para o idioma i 'estou trabalhando) e palavras -chave. Nesta lista será um item com o nome aa. Como uma primeira etapa para avaliar a expressão no número 1, selecionamos o item da enumeração de contexto com o nome aa, dando -nos um IDeclaration Para a próxima etapa.
  4. Em seguida, aplico o operador ao IDeclaration representando aa para conseguir outro IEnumerable<IDeclaration> contendo os "membros" (em certo sentido) de aa. Desde o . O operador é diferente do -> operador, eu ligo declaration.GetMembers(".") e espere o IDeclaration Objeta para aplicar corretamente o operador listado.
  5. Isso continua até eu bater cc, onde a lista de declaração posso ou não posso conter um objeto com o nome cc. Como tenho certeza que você está ciente, se vários itens começarem cc, eles devem aparecer também. Eu resolvo isso tomando a enumeração final e passando por ela Meu algoritmo documentado para fornecer ao usuário as informações mais úteis possíveis.

Aqui estão algumas notas adicionais para o back -end do Intellisense:

  • Eu faço uso extensivo dos mecanismos de avaliação preguiçosos da Linq na implementação GetMembers. Cada objeto no meu cache é capaz de fornecer um functor que avalia aos seus membros; portanto, executar ações complicadas com a árvore é quase trivial.
  • Em vez de cada objeto mantendo um List<IDeclaration> de seus membros, eu mantenho um List<Name>, Onde Name é uma estrutura contendo o hash de uma string especialmente formatada que descreve o membro. Há um cache enorme que mapeia nomes para objetos. Dessa forma, quando eu estou meu arquivo, posso remover todos os itens declarados no arquivo do cache e o repovoar com os membros atualizados. Devido à maneira como os funções são configurados, todas as expressões avaliam imediatamente os novos itens.

Intellisense "Frontend"

Como os tipos de usuário, o arquivo é sintaticamente incorreta com mais frequência do que está correto. Como tal, não quero remover seções ao acaso do cache quando o usuário digitar. Eu tenho um grande número de regras de caso especial para lidar com atualizações incrementais o mais rápido possível. O cache incremental é mantido local apenas em um arquivo aberto e ajuda a garantir que o usuário não perceba que a digitação está fazendo com que o cache de back -end mantenha informações incorretas na linha/coluna para coisas como cada método no arquivo.

  • Um fator redentor é o meu analisador é velozes. Ele pode lidar com uma atualização completa do cache de um arquivo de origem de linha de 20000 em 150ms enquanto opera independente em um encadeamento de fundo de baixa prioridade. Sempre que esse analisador conclui um passe em um arquivo aberto com sucesso (sintaticamente), o estado atual do arquivo é movido para o cache global.
  • Se o arquivo não estiver sintaticamente correto, eu uso um Analisador de filtro ANTLR (desculpe pelo link - a maioria das informações está na lista de discussão ou reunida na leitura da fonte) para reparar o arquivo procurando:
    • Declarações variáveis/de campo.
    • A assinatura para definições de classe/estrutura.
    • A assinatura para definições de método.
  • No cache local, as definições de classe/estrutura/método começam na assinatura e terminam quando o nível de nidificação da cinta remonta à unidade. Os métodos também podem terminar se outra declaração do método for atingida (sem métodos de nidificação).
  • No cache local, variáveis/campos estão vinculados ao imediatamente anterior não -deslocado elemento. Veja o breve snippet de código abaixo para obter um exemplo de por que isso é importante.
  • Além disso, como os tipos de usuário, mantenho uma tabela de remapeamento marcando os intervalos de caracteres adicionados/removidos. Isso é usado para:
    • Certifique -se de que eu possa identificar o contexto correto do cursor, pois um método pode/se move no arquivo entre parses completas.
    • Certifique -se de ir para a declaração/definição/referência localizar itens corretamente em arquivos abertos.

Snippet de código para a seção anterior:

class A
{
    int x; // linked to A

    void foo() // linked to A
    {
        int local; // linked to foo()

    // foo() ends here because bar() is starting
    void bar() // linked to A
    {
        int local2; // linked to bar()
    }

    int y; // linked again to A

Imaginei que adicionaria uma lista dos recursos IntelliSense que implementei com este layout. Imagens de cada um estão localizadas aqui.

  • Autocompletar
  • Dicas de ferramentas
  • Dicas de método
  • Visualização de classe
  • Janela de definição de código
  • Ligue para o navegador (vs 2010 finalmente adiciona isso a C#)
  • Semanticamente correto, encontre todas as referências

Outras dicas

Eu não posso dizer exatamente o que são algoritmos utilizados por qualquer aplicação particular, mas posso fazer algumas suposições.Um trie é uma forma muito útil de estrutura de dados para este problema:o IDE pode manter um grande trie na memória de todos os símbolos no seu projeto, com alguns extras de metadados em cada nó.

Quando você digita um caractere, ele percorre um caminho na trie.Todos os descendentes de um determinado trie nó são possíveis conclusões.O IDE, em seguida, só precisa de filtrar aqueles que saem por aqueles que fazem sentido no contexto actual, mas ele só precisa calcular quantos podem ser exibidos na guia de conclusão de janela pop-up.

Mais avançado-conclusão requer mais complicado trie.Por exemplo, O Visual Ajudá-X tem um recurso, no qual você só precisa digitar as letras maiúsculas de CamelCase símbolos -- por exemplo, se você digitar SFN, ele mostra o símbolo SomeFunctionName em sua guia-janela de conclusão.

Computação trie (ou outras estruturas de dados) requer a análise de todo o seu código para obter uma lista de todos os símbolos no seu projeto.O Visual Studio armazena-o no seu banco de dados do IntelliSense, um .ncb arquivo armazenado juntamente com o seu projeto, de modo que ele não tem para reanálise tudo cada vez que você fechar e reabrir o projeto.A primeira vez que você abrir um projeto grande (digamos, que você acabou sincronizados formulário de controle de origem), VS vai ter tempo para analisar tudo e gerar o banco de dados.

Eu não sei como ele lida com mudanças incrementais.Como você disse, quando você estiver escrevendo código, sintaxe inválida 90% do tempo, e reparsing tudo, sempre que você ociosa seria colocar um imposto enorme sobre sua CPU para pouco benefício, especialmente se você estiver modificando um arquivo de cabeçalho incluído por um grande número de arquivos de origem.

Eu suspeito que ele (a) só reparses sempre que você constrói o seu projeto (ou, eventualmente, quando você fechar/abrir), ou (b) ele faz algum tipo de local de análise, onde ele analisa o código em torno de onde você acabou editado em alguma forma limitada, apenas para obter os nomes dos símbolos relevantes.Uma vez que o C++ tem um tal extraordinariamente complicada gramática, ele pode se comportar estranhamente nos cantos escuros se você estiver usando pesado template metaprogramming e afins.

O link a seguir o ajudará ainda mais ..

Realce de sintaxe:Caixa de texto de cor rápida para destaque da sintaxe

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top