Ich schreibe ein COM-Add-In, das eine IDE erweitert, die es dringend benötigt. Es gibt viele Funktionen, aber lassen Sie es uns für diesen Beitrag auf 2 eingrenzen:
- Es gibt einen Code - Explorer Toolwindow , dass zeigt eine Baumansicht , die die Benutzer navigieren Module und ihre Mitglieder können.
- Es gibt ein Code - Inspektionen Toolwindow , dass zeigt eine Datagridview , die die Benutzer navigieren Code Probleme können und automatisch beheben.
Beide Tools verfügen über eine Schaltfläche "Aktualisieren", mit der eine asynchrone Aufgabe gestartet wird, mit der der gesamte Code in allen geöffneten Projekten analysiert wird. Der Code-Explorer verwendet die Analyseergebnisse, um die Baumansicht zu erstellen , und die Code-Inspektionen verwenden die Analyseergebnisse, um Codeprobleme zu finden und die Ergebnisse in der Datagrid-Ansicht anzuzeigen .
Was ich versuche , hier zu tun, ist die Parse - Ergebnisse zwischen den Funktionen zu teilen, dass so , wenn die Code - Explorer Auffrischungen, dann ist der Code - Inspektionen weiß es und kann sich erneuern , ohne die Analyse der Arbeit wiederholen zu müssen , dass der Code - Explorer habe gerade .
Also habe ich meine Parser-Klasse zu einem Ereignisanbieter gemacht, bei dem sich die Funktionen registrieren können:
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);
}
});
}
Und es funktioniert. Das Problem, das ich habe, ist, dass ... es funktioniert - ich meine, wenn die Codeinspektionen aktualisiert werden, sagt der Parser dem Code-Explorer (und allen anderen): "Alter, jemand analysiert, was möchten Sie dagegen tun?" "" - und wenn das Parsen abgeschlossen ist, sagt der Parser seinen Zuhörern: "Leute, ich habe neue Analyseergebnisse für Sie, was möchten Sie dagegen tun?".
Lassen Sie mich Sie durch ein Beispiel führen, um das dadurch entstehende Problem zu veranschaulichen:
- Der Benutzer ruft den Code-Explorer auf, der dem Benutzer mitteilt, dass "Moment, ich arbeite hier". Benutzer arbeitet weiterhin in der IDE, der Code Explorer zeichnet sich neu, das Leben ist schön.
- Der Benutzer ruft dann die Code-Inspektionen auf, die dem Benutzer mitteilen, dass "Warte, ich arbeite hier". Der Parser teilt dem Code Explorer mit: "Alter, jemand analysiert, was möchten Sie dagegen tun?" - Der Code Explorer teilt dem Benutzer mit, "Moment, ich arbeite hier". Der Benutzer kann weiterhin in der IDE arbeiten, jedoch nicht im Code-Explorer navigieren, da dieser aktualisiert wird. Und er wartet auch darauf, dass die Code-Inspektionen abgeschlossen sind.
- Der Benutzer sieht ein Codeproblem in den Prüfergebnissen, die er behandeln möchte. Sie doppelklicken, um dorthin zu navigieren, bestätigen, dass ein Problem mit dem Code vorliegt, und klicken auf die Schaltfläche "Beheben". Das Modul wurde geändert und muss erneut analysiert werden, damit die Code-Inspektionen fortgesetzt werden können. Der Code Explorer teilt dem Benutzer mit, "Moment, ich arbeite hier", ...
Sehen Sie, wohin das führt? Ich mag es nicht und ich wette, Benutzer werden es auch nicht mögen. Was vermisse ich? Wie soll ich vorgehen, um Analyseergebnisse zwischen Features zu teilen, aber dem Benutzer trotzdem die Kontrolle darüber zu überlassen, wann das Feature seine Arbeit erledigen soll ?
Der Grund, den ich frage, ist, dass ich dachte, wenn ich die eigentliche Arbeit verschieben würde, bis der Benutzer sich aktiv für eine Aktualisierung entscheidet, und die Analyseergebnisse "zwischengespeichert" habe, sobald sie eingehen ... nun, dann würde ich eine Baumansicht aktualisieren und Suchen von Codeproblemen in einem möglicherweise veralteten Analyseergebnis ... was mich buchstäblich zu Punkt eins zurückbringt, wo jedes Feature mit seinen eigenen Analyseergebnissen arbeitet: Gibt es eine Möglichkeit, Analyseergebnisse zwischen Features auszutauschen und eine schöne UX zu haben?
Der Code ist c # , aber ich suche keinen Code, ich suche nach Konzepten .
quelle
VBAParser
von ANTLR generiert und gibt mir einen Analysebaum, aber die Funktionen verbrauchen das nicht. DerRubberduckParser
nimmt den Analysebaum, geht ihn durch und gibt ein ausVBProjectParseResult
, dasDeclaration
Objekte enthält , derenReferences
Auflösung vollständig ist - das ist es, was die Funktionen für die Eingabe benötigen. Also ja, es ist so ziemlich eine Alles-oder-Nichts-Situation. DasRubberduckParser
ist klug genug, um Module, die nicht geändert wurden, nicht erneut zu analysieren. Aber wenn es einen Engpass gibt, liegt das nicht am Parsen, sondern an den Code-Inspektionen.Antworten:
Die Art und Weise, wie ich dies wahrscheinlich angehen würde, wäre, mich weniger auf perfekte Ergebnisse zu konzentrieren, sondern mich auf einen Best-Effort-Ansatz zu konzentrieren. Dies würde zumindest zu folgenden Änderungen führen:
Konvertieren Sie die Logik, die derzeit eine erneute Analyse startet, in eine Anforderung anstelle einer Initiierung.
Die Logik zum Anfordern einer erneuten Analyse sieht möglicherweise folgendermaßen aus:
Dies wird mit einer Logik gepaart, die den Parser umschließt. Dies könnte ungefähr so aussehen:
Wichtig ist, dass der Parser ausgeführt wird, bis die letzte Anforderung zum erneuten Analysieren berücksichtigt wurde, jedoch nicht mehr als ein Parser gleichzeitig ausgeführt wird.
Entfernen Sie den
ParseStarted
Rückruf. Das Anfordern einer erneuten Analyse ist jetzt eine Feuer-und-Vergessen-Operation.Alternativ können Sie es konvertieren, um nichts anderes zu tun, als einen Aktualisierungsindikator in einem abgelegenen Teil der GUI anzuzeigen, der die Benutzerinteraktion nicht blockiert.
Versuchen Sie, minimale Handhabung für veraltete Ergebnisse bereitzustellen.
Im Fall des Code-Explorers kann dies so einfach sein, dass eine angemessene Anzahl von Zeilen nach oben und unten nach einer Methode gesucht wird, zu der der Benutzer navigieren möchte, oder nach der nächsten Methode, wenn kein genauer Name gefunden wurde.
Ich bin mir nicht sicher, was für den Code Inspector angemessen wäre.
Ich bin mir der Implementierungsdetails nicht sicher, aber insgesamt ist dies sehr ähnlich wie der NetBeans-Editor mit diesem Verhalten umgeht. Es ist immer sehr schnell darauf hinzuweisen, dass es derzeit aktualisiert wird, aber auch den Zugriff auf die Funktionalität nicht blockiert.
Veraltete Ergebnisse sind oft gut genug - insbesondere im Vergleich zu keinen Ergebnissen.
quelle
ParseStarted
deaktiviere damit die Schaltfläche [Aktualisieren] (Control.EnableRefresh(false)
). Wenn ich diesen Rückruf entferne und den Benutzer darauf klicken lasse, würde ich mich in eine Situation versetzen, in der zwei Aufgaben gleichzeitig ausgeführt werden. Wie vermeide ich dies, ohne die Aktualisierung aller anderen Funktionen zu deaktivieren, während jemand analysiert?ParseStarted
Ereignis behalten würde , falls Sie der Benutzeroberfläche (oder einer anderen Komponente) erlauben möchten, den Benutzer manchmal vor einer Analyse zu warnen. Natürlich möchten Sie möglicherweise dokumentieren, dass Anrufer versuchen sollten, den Benutzer nicht daran zu hindern, die (kurz vor dem) veralteten aktuellen Analyseergebnisse zu verwenden.