J'écris un complément COM qui étend un IDE qui en a désespérément besoin. Il y a beaucoup de fonctionnalités impliquées, mais réduisons-le à 2 pour les besoins de ce post:
- Il existe une fenêtre d'outils Explorateur de code qui affiche une arborescence qui permet à l'utilisateur de parcourir les modules et leurs membres.
- Il existe une fenêtre d'outils Inspections de code qui affiche une vue de la grille de données qui permet à l'utilisateur de parcourir les problèmes de code et de les résoudre automatiquement.
Les deux outils ont un bouton "Actualiser" qui démarre une tâche asynchrone qui analyse tout le code dans tous les projets ouverts; l' Explorateur de code utilise les résultats de l'analyse pour créer l' arborescence et les inspections de code utilisent les résultats de l'analyse pour rechercher les problèmes de code et afficher les résultats dans sa vue de datagrid .
Ce que j'essaie de faire ici, c'est de partager les résultats de l'analyse entre les fonctionnalités, de sorte que lorsque l' explorateur de code est actualisé, les inspections de code le savent et puissent se rafraîchir sans avoir à refaire le travail d'analyse que l' explorateur de code vient de faire. .
Donc, ce que j'ai fait, j'ai fait de ma classe d'analyseur un fournisseur d'événements auquel les fonctionnalités peuvent s'inscrire:
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);
}
});
}
Et il fonctionne. Le problème que j'ai, c'est que ... ça marche - je veux dire, quand les inspections de code sont actualisées, l'analyseur dit à l'explorateur de code (et à tout le monde) "mec, analyse de quelqu'un, quoi que ce soit que vous voulez faire à ce sujet? " - et lorsque l'analyse est terminée, l'analyseur dit à ses auditeurs "les gars, j'ai de nouveaux résultats d'analyse pour vous, quoi que ce soit que vous vouliez faire à ce sujet?".
Permettez-moi de vous expliquer un exemple pour illustrer le problème que cela crée:
- L'utilisateur affiche l'explorateur de code, qui dit à l'utilisateur "attendez, je travaille ici"; l'utilisateur continue de travailler dans l'IDE, l'explorateur de code se redessine, la vie est belle.
- L'utilisateur fait ensuite apparaître les inspections de code, qui indiquent à l'utilisateur «attendez, je travaille ici»; l'analyseur dit à l'explorateur de code "mec, l'analyse de quelqu'un, qu'est-ce que vous voulez faire à ce sujet?" - l'Explorateur de code dit à l'utilisateur "attendez, je travaille ici"; l'utilisateur peut toujours travailler dans l'EDI, mais ne peut pas naviguer dans l'explorateur de code car il est rafraîchissant. Et il attend également la fin des inspections de code.
- L'utilisateur voit un problème de code dans les résultats d'inspection qu'il souhaite résoudre; ils double-cliquent dessus pour y accéder, confirment qu'il y a un problème avec le code et cliquent sur le bouton "Corriger". Le module a été modifié et doit être analysé de nouveau, de sorte que les inspections du code se poursuivent; l'explorateur de code dit à l'utilisateur "attendez, je travaille ici", ...
Vous voyez où cela va? Je n'aime pas ça, et je parie que les utilisateurs ne l'aimeront pas non plus. Qu'est-ce que je rate? Comment dois-je procéder pour partager les résultats d'analyse entre les fonctionnalités, tout en laissant à l'utilisateur le contrôle du moment où la fonctionnalité doit faire son travail ?
La raison pour laquelle je demande, c'est parce que je me suis dit que si je reportais le travail réel jusqu'à ce que l'utilisateur décide activement de rafraîchir, et "mis en cache" les résultats de l'analyse à mesure qu'ils entrent ... eh bien, je rafraîchirais un aperçu et localiser les problèmes de code dans un résultat d'analyse éventuellement périmé ... ce qui me ramène littéralement à la case départ, où chaque fonctionnalité fonctionne avec ses propres résultats d'analyse: est-il possible de partager les résultats d'analyse entre les fonctionnalités et d' avoir une belle UX?
Le code est c # , mais je ne cherche pas de code, je cherche des concepts .
la source
VBAParser
est généré par ANTLR et me donne un arbre d'analyse, mais les fonctionnalités ne consomment pas cela. LeRubberduckParser
prend l'arbre d'analyse, le parcourt et émet unVBProjectParseResult
qui contient tous lesDeclaration
objetsReferences
résolus - c'est ce que les fonctionnalités prennent pour la saisie .. alors oui, c'est à peu près une situation tout ou rien. LeRubberduckParser
est assez intelligent pour ne pas réanalyser les modules qui n'ont pas été modifiés cependant. Mais s'il y a un goulot d'étranglement, ce n'est pas avec l'analyse, c'est avec les inspections de code.Réponses:
La façon dont j'aborderais probablement cela serait de se concentrer moins sur la fourniture de résultats parfaits, et plutôt de se concentrer sur une approche au mieux. Cela entraînerait au moins les modifications suivantes:
Convertissez la logique qui démarre actuellement une nouvelle analyse pour demander au lieu de lancer.
La logique pour demander une nouvelle analyse peut finir par ressembler à ceci:
Cela sera associé à une logique enveloppant l'analyseur, qui peut ressembler à ceci:
L'important est que l'analyseur s'exécute jusqu'à ce que la dernière demande d'analyse soit honorée, mais pas plus d'un analyseur ne s'exécute à la fois.
Supprimez le
ParseStarted
rappel. Demander une nouvelle analyse est maintenant une opération d'incendie et d'oubli.Alternativement, convertissez-le pour ne rien faire d'autre que d'afficher un indicateur rafraîchissant dans une partie de l'interface graphique qui ne bloque pas l'interaction de l'utilisateur.
Essayez de fournir une manipulation minimale pour des résultats périmés.
Dans le cas de l'explorateur de code, cela peut être aussi simple que de rechercher un nombre raisonnable de lignes de haut en bas pour une méthode vers laquelle l'utilisateur souhaite naviguer, ou la méthode la plus proche si un nom exact n'a pas été trouvé.
Je ne sais pas ce qui serait approprié pour l'inspecteur de code.
Je ne suis pas sûr des détails de l'implémentation, mais dans l'ensemble, cela ressemble beaucoup à la façon dont l'éditeur NetBeans gère ce comportement. Il est toujours très rapide de souligner qu'il est actuellement rafraîchissant, mais ne bloque pas non plus l'accès à la fonctionnalité.
Les résultats périmés sont souvent assez bons - surtout par rapport à l'absence de résultats.
la source
ParseStarted
pour désactiver le bouton [Actualiser] (Control.EnableRefresh(false)
). Si je supprime ce rappel et laisse l'utilisateur cliquer dessus ... alors je me mettrais dans une situation où j'ai deux tâches simultanées effectuant l'analyse ... comment éviter cela sans désactiver l'actualisation de toutes les autres fonctionnalités pendant que quelqu'un est l'analyse?ParseStarted
événement, au cas où vous voudriez permettre à l'interface utilisateur (ou à un autre composant) d'avertir parfois l'utilisateur qu'une analyse se produit. Bien sûr, vous souhaiterez peut-être documenter les appelants qui devraient essayer de ne pas empêcher l'utilisateur d'utiliser les résultats d'analyse actuels (sur le point d'être périmés).