Estou escrevendo um suplemento de COM que estende um IDE que precisa desesperadamente dele. Existem muitos recursos envolvidos, mas vamos reduzi-lo para 2 por causa deste post:
- Há uma janela de ferramenta do Code Explorer que exibe uma visualização em árvore que permite ao usuário navegar pelos módulos e seus membros.
- Há uma janela da ferramenta Inspeções de código que exibe uma visualização de datagrid que permite ao usuário navegar pelos problemas de código e corrigi-los automaticamente.
Ambas as ferramentas têm um botão "Atualizar" que inicia uma tarefa assíncrona que analisa todo o código em todos os projetos abertos; o Code Explorer usa os resultados da análise para criar a visualização em árvore , e o Code Inspections usa os resultados da análise para encontrar problemas de código e exibir os resultados em sua visualização de datagrid .
O que estou tentando fazer aqui é compartilhar os resultados da análise entre os recursos, para que, quando o Code Explorer for atualizado, as Inspeções de Código o reconheçam e possam se atualizar sem precisar refazer o trabalho de análise que o Code Explorer acabou de realizar. .
Então, o que fiz foi transformar minha classe de analisador em um provedor de eventos no qual os recursos podem ser registrados:
private void _parser_ParseCompleted(object sender, ParseCompletedEventArgs e)
{
Control.Invoke((MethodInvoker) delegate
{
Control.SolutionTree.Nodes.Clear();
foreach (var result in e.ParseResults)
{
var node = new TreeNode(result.Project.Name);
node.ImageKey = "Hourglass";
node.SelectedImageKey = node.ImageKey;
AddProjectNodes(result, node);
Control.SolutionTree.Nodes.Add(node);
}
Control.EnableRefresh();
});
}
private void _parser_ParseStarted(object sender, ParseStartedEventArgs e)
{
Control.Invoke((MethodInvoker) delegate
{
Control.EnableRefresh(false);
Control.SolutionTree.Nodes.Clear();
foreach (var name in e.ProjectNames)
{
var node = new TreeNode(name + " (parsing...)");
node.ImageKey = "Hourglass";
node.SelectedImageKey = node.ImageKey;
Control.SolutionTree.Nodes.Add(node);
}
});
}
E isso funciona. O problema que estou tendo é que ... funciona - quero dizer, quando as inspeções de código são atualizadas, o analisador diz ao explorador de código (e a todos os outros) "cara, alguém está analisando, alguma coisa que você queira fazer? " - e quando a análise é concluída, o analisador diz a seus ouvintes "pessoal, eu tenho novos resultados de análise para você, qualquer coisa que você queira fazer sobre isso?".
Deixe-me mostrar um exemplo para ilustrar o problema que isso cria:
- O usuário abre o Code Explorer, que informa ao usuário "espere, estou trabalhando aqui"; O usuário continua trabalhando no IDE, o Code Explorer se redesenha, a vida é linda.
- O usuário então exibe as Inspeções de código, que dizem ao usuário "espere, estou trabalhando aqui"; o analisador diz ao Code Explorer "cara, alguém está analisando, qualquer coisa que você queira fazer sobre isso?" - o Code Explorer diz ao usuário "espere, estou trabalhando aqui"; o usuário ainda pode trabalhar no IDE, mas não pode navegar no Code Explorer porque está atualizando. E ele também está aguardando a conclusão das inspeções de código.
- O usuário vê um problema de código nos resultados da inspeção que deseja resolver; eles clicam duas vezes para navegar até ele, confirmam que há um problema com o código e clique no botão "Corrigir". O módulo foi modificado e precisa ser analisado novamente, para que as inspeções de código continuem com ele; o Code Explorer diz ao usuário "espere, estou trabalhando aqui", ...
Veja onde isso está indo? Eu não gosto, e aposto que os usuários também não vão gostar. o que estou perdendo? Como devo compartilhar os resultados da análise entre os recursos, mas ainda deixar o usuário no controle de quando o recurso deve fazer seu trabalho ?
A razão pela qual estou perguntando é porque achei que se eu adiasse o trabalho real até o usuário decidir ativamente atualizar e "armazenasse em cache" os resultados da análise à medida que eles chegassem ... bem, eu estaria atualizando uma exibição em árvore e localizando problemas de código em um resultado de análise possivelmente obsoleto ... o que literalmente me leva de volta à estaca zero, onde cada recurso trabalha com seus próprios resultados de análise: existe alguma maneira de compartilhar resultados de análise entre recursos e ter um UX adorável?
O código é c # , mas não estou procurando por código, estou procurando por conceitos .
fonte
VBAParser
é gerada pela ANTLR e me fornece uma árvore de análise, mas os recursos não consomem isso. EleRubberduckParser
pega a árvore de análise, a percorre e emite umVBProjectParseResult
que contémDeclaration
objetos que foram todosReferences
resolvidos - é isso que os recursos levam para entrada .. então sim, é praticamente uma situação de tudo ou nada. ORubberduckParser
é inteligente o suficiente para não analisar novamente os módulos que não foram modificados. Mas se houver um gargalo, não é a análise, é a inspeção do código.Respostas:
A maneira como eu provavelmente abordaria isso seria focar menos em fornecer resultados perfeitos e, em vez disso, focar em uma abordagem de melhor esforço. Isso resultaria em pelo menos as seguintes alterações:
Converta a lógica que atualmente inicia uma nova análise para solicitar, em vez de iniciar.
A lógica para solicitar uma nova análise pode acabar parecida com esta:
Isso será emparelhado com a lógica que envolve o analisador, que pode ser algo como isto:
O importante é que o analisador seja executado até que a solicitação de análise mais recente seja atendida, mas não mais do que um analisador esteja sendo executado a qualquer momento.
Remova o
ParseStarted
retorno de chamada. Solicitar uma nova análise agora é uma operação de ignorar e esquecer.Como alternativa, converta-o para não fazer nada além de mostrar um indicador refrescante em alguma parte da GUI que não bloqueia a interação do usuário.
Tente fornecer um tratamento mínimo para resultados obsoletos.
No caso do Code Explorer, isso pode ser tão simples quanto procurar um número razoável de linhas para cima e para baixo para um método para o qual o usuário deseja navegar ou o método mais próximo se um nome exato não for encontrado.
Não tenho certeza do que seria apropriado para o Code Inspector.
Não tenho certeza dos detalhes da implementação, mas, no geral, isso é muito parecido com o modo como o editor NetBeans lida com esse comportamento. É sempre muito rápido ressaltar que está atualizando, mas também não bloqueia o acesso à funcionalidade.
Resultados obsoletos geralmente são bons o suficiente - especialmente quando comparados a nenhum resultado.
fonte
ParseStarted
para desativar o botão [Atualizar] (Control.EnableRefresh(false)
). Se eu remover esse retorno de chamada e permitir que o usuário clique nele ... então me colocaria em uma situação em que tenho duas tarefas simultâneas fazendo a análise ... como evito isso sem desativar a atualização de todas as outras funcionalidades enquanto alguém está analisando?ParseStarted
evento, caso você queira permitir que a interface do usuário (ou outro componente) avise algumas vezes ao usuário que uma nova análise está ocorrendo. Claro, você pode querer chamadores documento deve tentar não parar o usuário de usar o (prestes a ser) resultados de análise atuais obsoletos.