Piszę dodatek COM, który rozszerza IDE, które desperacko tego potrzebuje. W grę wchodzi wiele funkcji, ale ograniczmy się do 2 ze względu na ten post:
- Istnieje okno narzędzi Code Explorer, które wyświetla widok drzewa, który pozwala użytkownikowi nawigować po modułach i ich członkach.
- Jest Kontrole Code toolwindow który wyświetla DataGridView , który pozwala poruszać się problemy kodu użytkownika i automatycznie je naprawić.
Oba narzędzia mają przycisk „Odśwież”, który uruchamia asynchroniczne zadanie, które analizuje cały kod we wszystkich otwartych projektach; Code Explorer używa analizowania wyników zbudować katalogów , a kod Kontrole wykorzystuje analizowania wyników, aby znaleźć problemy kodu i wyświetla wyniki w swojej DataGridView .
To, co próbuję tutaj zrobić, to udostępnienie wyników analizy między funkcjami, aby po odświeżeniu Code Explorera , Inspekcje kodu wiedziały o tym i mogły się odświeżyć bez konieczności ponownego wykonywania analizy składniowej, którą właśnie wykonał Code Explorer .
Więc co zrobiłem, uczyniłem moją klasę analizatora składni zdarzeniem, do którego funkcje mogą się zarejestrować:
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);
}
});
}
I to działa. Problem, który mam, polega na tym, że ... działa - to znaczy, kiedy inspekcje kodu są odświeżane, parser mówi eksploratorowi kodu (i wszystkim innym) "koleś, czyjaś analiza, czy chcesz coś z tym zrobić? „ - a kiedy parsowanie się zakończy, parser mówi swoim słuchaczom: „chłopaki, mam dla ciebie świeże wyniki parsowania, cokolwiek chcesz z tym zrobić?”.
Pozwól, że przedstawię ci przykład ilustrujący problem, który to stwarza:
- Użytkownik wywołuje Eksploratora kodu, który mówi użytkownikowi „poczekaj, pracuję tutaj”; użytkownik kontynuuje pracę w IDE, Code Explorer przerysowuje się, życie jest piękne.
- Następnie użytkownik wywołuje Inspekcje kodu, które mówią użytkownikowi „poczekaj, pracuję tutaj”; parser mówi Eksploratorowi kodu „koleś, czyjaś analiza, czy chcesz coś z tym zrobić?” - Code Explorer mówi użytkownikowi „poczekaj, pracuję tutaj”; użytkownik nadal może pracować w środowisku IDE, ale nie może poruszać się po Eksploratorze kodu, ponieważ jest odświeżany. I czeka też na zakończenie kontroli kodu.
- Użytkownik widzi problem z kodem w wynikach kontroli, którą chce rozwiązać; kliknij dwukrotnie, aby przejść do niego, potwierdź problem z kodem i kliknij przycisk „Napraw”. Moduł został zmodyfikowany i musi zostać ponownie przeanalizowany, aby umożliwić kontynuację inspekcji kodu; Code Explorer mówi użytkownikowi „poczekaj, pracuję tutaj”, ...
Widzisz dokąd to zmierza? Nie podoba mi się to i założę się, że użytkownikom też się nie spodoba. czego mi brakuje? Jak powinienem udostępniać wyniki analizy między funkcjami, ale nadal pozostawić kontrolę nad tym, kiedy funkcja powinna działać ?
Powodem, dla którego pytam, jest to, że pomyślałem, że jeśli odłożę rzeczywistą pracę, aż użytkownik aktywnie zdecyduje się odświeżyć, i „buforuję” wyniki analizy, gdy się pojawią… cóż, odświeżyłbym widok drzewa i lokalizowanie problemów z kodem w prawdopodobnie przestarzałym wyniku analizy ... co dosłownie sprowadza mnie z powrotem do punktu wyjścia, gdzie każda funkcja działa z własnymi wynikami analizy: czy jest jakiś sposób, aby udostępnić wyniki analizy między funkcjami i mieć piękny UX?
Kod to c # , ale nie szukam kodu, szukam pojęć .
źródło
VBAParser
jest generowany przez ANTLR i daje mi parsowanie, ale funkcje tego nie zużywają.RubberduckParser
Bierze drzewo składniowy, spacery, i zgłosiVBProjectParseResult
, który zawieraDeclaration
obiekty, które posiadają wszystkie ichReferences
rozwiązany - to jakie cechy biorą na wejściu .. tak tak, to dość dużo sytuacja wszystko albo nic.RubberduckParser
Jest wystarczająco inteligentny, aby nie re-parse modułów, które nie zostały zmodyfikowane chociaż. Ale jeśli istnieje wąskie gardło, nie dotyczy to analizowania, lecz kontroli kodu.Odpowiedzi:
Sposób, w jaki prawdopodobnie do tego podchodziłbym, polegałby na mniejszym skupieniu się na zapewnianiu doskonałych rezultatów, a zamiast tego na podejściu opartym na najlepszym wysiłku. Spowodowałoby to co najmniej następujące zmiany:
Przekształć logikę, która obecnie rozpoczyna ponowną analizę, aby zażądać zamiast zainicjować.
Logika żądania ponownego przeanalizowania może wyglądać mniej więcej tak:
Zostanie to połączone z logiką opakowującą parser, która może wyglądać mniej więcej tak:
Ważną rzeczą jest to, że analizator składni działa, dopóki nie zostanie spełnione ostatnie żądanie ponownej analizy, ale w danym momencie nie działa więcej niż jeden analizator składni.
Usuń połączenie
ParseStarted
zwrotne. Żądanie ponownej analizy jest teraz operacją typu „zapomnij”.Ewentualnie przekonwertuj go, aby nie robił nic poza pokazywaniem wskaźnika odświeżania w części drogi GUI, która nie blokuje interakcji użytkownika.
Spróbuj zapewnić minimalną obsługę nieaktualnych wyników.
W przypadku Eksploratora kodu może to być tak proste, jak szukanie rozsądnej liczby wierszy w górę iw dół dla metody, do której użytkownik chce nawigować, lub najbliższej metody, jeśli nie znaleziono dokładnej nazwy.
Nie jestem pewien, co byłoby odpowiednie dla Inspektora kodu.
Nie jestem pewien szczegółów implementacji, ale ogólnie rzecz biorąc, jest to bardzo podobne do tego, jak edytor NetBeans obsługuje to zachowanie. Zawsze bardzo szybko można zauważyć, że obecnie jest ono odświeżane, ale również nie blokuje dostępu do funkcji.
Stale wyniki są często wystarczająco dobre - zwłaszcza w porównaniu z brakiem wyników.
źródło
ParseStarted
do wyłączenia przycisku [Odśwież] (Control.EnableRefresh(false)
). Jeśli usunę to wywołanie zwrotne i pozwolę użytkownikowi je kliknąć ... wtedy postawiłbym się w sytuacji, w której mam dwa równoległe zadania, które parsują ... jak tego uniknąć, nie wyłączając odświeżania wszystkich innych funkcji, gdy ktoś parsuje?ParseStarted
zdarzenie, na wypadek gdybyś chciał pozwolić interfejsowi użytkownika (lub innemu składnikowi) czasami ostrzegać użytkownika o ponownej próbie. Oczywiście możesz udokumentować, że osoby dzwoniące powinny próbować nie powstrzymywać użytkownika od używania (prawdopodobnie być) nieaktualnych bieżących wyników analizy.